目录
UDP 通信流程
udp通信协议的流程和实现步骤
发送端:socket --》bind --》sendto --》close
接收端:socket --》bind --》recvfrom --》close
UDP 发送端解释
创建套接字 (socket)
int socket(int domain, int type, int protocol);
返回值:
成功 返回套接字的文件描述符
失败 -1
参数:
domain //地址协议族
/*
AF_INET //ipv4地址
AF_INET6 //ipv6地址
*/
type //套接字类型
/*
udp套接字 SOCK_DGRAM
tcp套接字 SOCK_STREAM
*/
protocol //扩展协议,一般设置为0
绑定ip和端口(bind)
int bind(int socket,const struct sockaddr* my_addr,int addrlen);
返回值:
成功 0
失败 -1
参数:
socket //刚才创建好的套接字
my_addr //保存需要绑定ip和端口号
struct sockaddr //通用地址结构体,兼容ipv4和ipv6的
struct sockaddr_in //ipv4地址结构体
{
sin_family; //地址协议族AF_INET
sin_addr; //它也是个结构体,存放要绑定的ip
{
s_addr; //最终用来存放ip地址
}
sin_port; //存放要绑定的端口号
}
struct sockaddr_in6 //ipv6地址结构体
addrlen //地址结构体大小 sizeof
ip地址和端口号的转换:
- 大端序:数据的高字节存放在低地址,低字节存放在高地址
- 小端序:数据的高字节存放在高地址,低字节存放在低地址
- linux系统中:数据采用小端序存储
- 计算机网络中:数据采用大端序存储
第一个:转换ip地址
in_addr_t inet_addr(const char* strptr);
返回值:
转换成功赋值给ipv4地址结构体中sin_addr
参数:
strptr //要转换的ip地址
第二个:转换端口号
u_short htons(u_short hostshort);
返回值:
转换成功赋值给ipv4地址结构体中sin_port
参数:
hostshort //你指定的端口号
发送信息(sendto)
int sendto(int sock ,void * msg,int len,unsigned int flags,struct sockaddr *to ,int tolen)
返回值:
成功 返回发送的字节数
失败 -1
参数:
sock //刚才创建的套接字
msg //要发送的信息
len //发送多少字节的信息
flags //默认设置为0
to //存放对方的ip和端口号
tolen //地址结构体大小 sizeof()
关闭套接字(close)
#include <unistd.h>
int close(int fildes);
参数:
fildes //套接字文件描述符
UDP 接收端解释
由于创建套接字、绑定IP和端口 跟发送端基本一致,所以这里不再贴出代码。可以参考总体代码。
接收信息(recvfrom)
#include <sys/socket.h>
ssize_t recvfrom(int socket,void *buffer,size_t length,int flags, struct sockaddr *address,socklen_t *address_len);
返回值:
参数:
socket //刚才创建的套接字
buffer //保存接收的信息
length //接收多少字节的数据
flags //默认设置为0
address //存放对方的ip和端口号
address_len //地址结构体大小,要求是指针
注意:udp 接收端除了接收信息部分不一样,其余都跟发送端一样。
具体代码
//发送端
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define N 50
int main(){
int socket;
int ret;
//定义数组保存输入的字符串信息
char buf[N];
//定义ipv4地址结构体变量存放自己ip和端口号
struct sockaddr_in bindaddr;
bindaddr.sin_family = AF_INET;
bindaddr.sin_addr.s_addr = inet_addr("192.168.1.1"); //绑定自己的ip
bindaddr.sin_port = htons(10081); //指定一个通信端口号
//定义ipv4地址结构体变量存放对方的ip和端口号
struct sockadder_in otheraddr;
otheraddr.sin_family = AF_INET;
otheraddr.sin_addr.s_addr = inet_addr("192.168.1.2"); //对方的ip
otheraddr.sin_port = htons(10082); //对方的端口号
//创建套接字
socket = socket(AF_INET, SOCK_DGRAM, 0);
if(socket == -1)
{
printf(" created udp_socket faliure!\n");
}
//绑定ubuntu自己ip和端口号
ret = bind(socket, &bindaddr,sizeof(bindaddr));
if(ret == -1)
{
printf(" bind ip_port faliure! \n");
}
//发送信息给对方-->读取键盘的输入,发送给对方
while(1)
{
printf(" please input the msg: ");
scanf("%s",buf);
sendto(socket, buf, strlen(buf), 0, &otheraddr,sizeof(otheraddr));
}
//关闭套接字
close(socket);
return 0;
}
//udp接收端:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#define N 50
int main()
{
int socket;
int ret;
//定义数组保存接收的信息
char buf[N];
//定义ipv4地址结构体变量存放自己ip和端口号
struct sockaddr_in bindaddr;
bindaddr.sin_family=AF_INET;
bindaddr.sin_addr.s_addr=inet_addr("192.168.1.2"); //绑定自己的ip
bindaddr.sin_port=htons(10081); //指定一个端口号
//定义ipv4地址结构体变量存放对方的ip和端口号
struct sockaddr_in otheraddr;
int addrsize=sizeof(otheraddr);
//创建套接字
socket = socket(AF_INET,SOCK_DGRAM,0);
if(socket == -1)
{
printf("created udp socket faliure !\n");
return -1;
}
//绑定ubuntu自己ip和端口号
ret = bind(udpsock, (struct sockaddr *)&bindaddr, sizeof(bindaddr));
if(ret == -1)
{
printf(" bind ip_port faliure!\n");
return -1;
}
//接收别人发送给我的信息
while(1)
{
//清空数组
bzero(buf,50);
recvfrom(udpsock, buf, 50, 0, (struct sockaddr *)&otheraddr, &addrsize);
printf(" recv msg: %s\n ", buf);
}
//关闭套接字
close(socket);
return 0;
}
TCP / IP 通信流程
TCP / IP 通信协议的流程和实现步骤:
发送端:socket --》gethostbyname--》connect --》send --》close
接收端:socket --》bind --》listen --》accept --》 recv --》close
TCP 发送端解释
创建套接字(socket)
int sockfd;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)//注意的是:这里与udp的区别是 字节流不同 (SOCK_STREAM)
{
perror("socket");
exit(1);
}
获取主机ip(gethostbyname)
struct hostent *gethostbyname(const char *name);
参数:
name //指向主机名的指针
返回值:
成功 主机地址
失败 NULL
if(argc < 2){
fprintf(stderr,"Please enter the server's hostname!\n");
exit(1);
}
if((host=gethostbyname(argv[1]))==NULL){
// struct hostent *host = gethostbyname("www.baidu.com");suo ding server ip
perror("gethostbyname");
exit(1);
}
建立连接(connect)
/*
connect函数解释:
*/
int connect(int socket, const struct sockaddr *address, socklen_t address_len);
参数:
socket //套接字
*address //指向要连接套接字的sockaddr结构体的指针 //套接字想要连接的主机地址和端口号
address_len //sockaddr结构的字节长度
返回值:
成功 返回0
失败 返回SOCKET_ERROR 错误
if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1 )
{
perror("connect");
exit(1);
}
读取键盘输入的信息(fgets)
char *fgets(char *s, int size, FILE *stream);
参数:
*s //指向一个字符数组的指针,该数组存储了要读取的字符串
size //读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
stream //指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。
发送数据(send)
ssize_t send(int socket, const void *buffer, size_t length, int flags);
参数:
socket //指定发送端套接字描述符
*buffer //指明一个存放应用程式要发送数据的缓冲区
length //指明实际要发送的数据的字符数
flags //一般置为0
返回值:
成功 已将信息发送到网络中
失败 -1
关闭套接字(close)
同udp,这里不作详细介绍。
TCP 接收端解释
创建套接字部分同上,绑定ip部分与udp相同。
监听(listen)
int listen(int socket, int backlog);
参数:
socket //套接字
backlog //等待连接队列的最大长度
返回值:
成功 0
失败 -1
接受连接(accept)
int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
参数:
socket //套接字描述符,该套接口在listen()后监听连接
*restrict address //(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定
*restrict address_len //(可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数
返回值:
成功 则accept()返回一个描述所接受包的SOCKET类型的值
失败 返回INVALID_SOCKET错误
接收信息(recv)
ssize_t recv(int socket, void *buffer, size_t length, int flags);
参数:
socket //指定接收端套接字描述符
*buffer //指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据
length //指明buf的长度
flags //一般置0
返回值:
成功 返回其实际copy的字节数
失败 -1
具体代码
TCP / IP 发送端(client):
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define SERVPORT 13 //目前常用的端口号为10000以内 所以我们尽可能设为10000以后的数字为端口号
#define MAXDATASIZE 100
main(int argc,char *argv[])
{
int sockfd,sendbytes;
char buf[MAXDATASIZE];
struct hostent *host;
struct sockaddr_in serv_addr;
if(argc < 2){
fprintf(stderr,"Please enter the server's hostname!\n"); //输入对方的ip
exit(1);
}
if((host=gethostbyname(argv[1]))==NULL){
// struct hostent *host = gethostbyname("www.baidu.com");//server ip
perror("gethostbyname");
exit(1);
}
//创建套接字
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
perror("socket");
exit(1);
}
//绑定ip和端口
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(SERVPORT);
serv_addr.sin_addr=*((struct in_addr *)host->h_addr);
//清除ip缓冲区
bzero(&(serv_addr.sin_zero),8);
//建立连接
if(connect(sockfd,(struct sockaddr *)&serv_addr,\
sizeof(struct sockaddr))==-1){
perror("connect");
exit(1);
}
//判断对方发过来的信息
if((sendbytes=recv(sockfd,buf,MAXDATASIZE,0))==-1){
perror("recv");
exit(1);
}
//结束符
buf[sendbytes] = '\0';
printf("received sys msg from server:%s",buf);
printf("\n");
while (1)
{
printf("Enter the message : ");
//键盘读入数据
if(fgets(buf, sizeof(buf) - 1, stdin) == NULL)
{
break;
}
//发送数据
if((sendbytes=send(sockfd,buf,strlen(buf),0))==-1){
perror("send");
exit(1);
}
printf("sent %d bytes \n", sendbytes);
}
close(sockfd);
}
TCP / IP 接收端(server):
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "time.h"
#define SERVPORT 13
#define BACKLOG 10
#define MAX_CONNECTED_NO 10
#define MAXDATASIZE 100
int main()
{
struct sockaddr_in server_sockaddr,client_sockaddr;
int sin_size,recvbytes;
int sockfd,client_fd;
time_t currentTime; //当前北京时间
char timebuffer[MAXDATASIZE];
char *p = "you have login successflly."; //建立连接成功
char buf[MAXDATASIZE];
//创建套接字
if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1)
{
perror("socket");
exit(1);
}
printf("socket success!,sockfd=%d\n",sockfd);
//绑定ip和端口号
server_sockaddr.sin_family=AF_INET;
server_sockaddr.sin_port=htons(SERVPORT);
server_sockaddr.sin_addr.s_addr=INADDR_ANY;
bzero(&(server_sockaddr.sin_zero),8);
if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))==-1){
perror("bind");
exit(1);
}
printf("bind success!\n");
//监听对方(信道)
if(listen(sockfd,BACKLOG)==-1){
perror("listen");
exit(1);
}
printf("listening....\n");
sin_size = sizeof(struct sockaddr);
//接受连接
if((client_fd=accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size))==-1){
perror("accept");
exit(1);
}
/***************get loclatime***************/
memset(timebuffer, 0,MAXDATASIZE);
currentTime=time(NULL);
snprintf(timebuffer,MAXDATASIZE,"%s\n",ctime(¤tTime));
//将时间写入buf
if ((recvbytes =write(client_fd,timebuffer, strlen(timebuffer))) <0 ) {
perror("write");
exit(1);
}
//
if ((recvbytes =write(client_fd,p, strlen(p))) <0 ) {
perror("write");
exit(1);
}
while (1) {
//接收信息
if((recvbytes=recv(client_fd,buf,MAXDATASIZE,0))==-1){
perror("recv");
exit(1);
}
buf[recvbytes] = '\0';
printf("received msg from clients :%s\n",buf);
}
close(sockfd);
}
//注意:本文中的函数基本可通过WSAGetLastError()获取相应错误代码
TCP & UDP 的对比
UDP 主要特点
- UDP 是无连接的,没有建立连接和释放连接的过程。
- UDP 只能尽最大努力交付,提供不可靠的传输服务。对于出现差错的报文进行丢弃处理。
- UDP 是面向报文的,与应用层交付的是完整的报文,既不合并,也不拆分,保留原始报文的边界。(因此应用层必须选择适合的报文长度,以免在IP 层进行分片工作,从而降低IP 层的效率)
- UDP 没有拥堵控制功能。(即使网络出现拥堵也不会使源主机降低发送效率,宁可丢失数据,也不允许传送的数据存在较大的延时)(视频会议等)
- UDP 首部简短,只有8B,减少了通信开销。
举例:对于某些实时应用,因使用无拥堵功能的 UDP 可能会引起网络的严重拥堵,而使用户无法正常接收。同样实时应用也会要求减少数据的丢失,这需要对 UDP 的不可靠传输进行适当的改进,如前向纠错或重传等措施。
总体来说,UDP 协议适用于发送短报文又不关心可靠性的场合。应用层的 DSN(数据平滑网络)、RIP(路由信息协议)、SNMP(简单网络管理协议) 的协议都是用UDP。 UDP 也适用于具有流量控制和差错控制机制的应用层协议。如简单的文件传送协议 TFTP。
TCP 主要特点
- TCP 是面向连接的。应用进程之间通信必须先后经历建立连接、数据传输和释放连接 3 个阶段。
- 应用进程之间的通信是通过 TCP 连接来进行的。每条 TCP 连接有两个端点,只能实现点 - 点通信。
- TCP 提供可靠交付的服务。也就是通过 TCP 连接传输的数据,不存在差错、丢失和重复现象,能按序到达目的端。
- TCP 提供全双工通信。TCP 允许通信双方的应用进程同时发送数据。在 TCP 连接两端都设有发送缓存和接受缓存,缓存时发送(或接收)数据的临时存放点。
- TCP 面向字节流的。“面向字节流”的含义是:TCP 把应用层下传给传输层的数据块看作是一串无结构的字节序列流。
TCP 不保证接收端应用进程收到的数据块与发送端的发出的数据块有着对应的大小的关系,但它保证接收端应用进程收到的字节流和发送端发出的字节流是完全一样的。即 TCP 创建了一种环境,使得发送应用进程与接收进程之间好像又一条假象的“隧道”,而这条隧道上传送的是以字节流形式的数据。
总结
以上特点说明,TCP 和 UDP 有着完全不同的传输协议机制。就报文而言,TCP 传输实体根据接收端给出的窗口大小和当前网络的拥塞程度来决定一个报文段应该分包含多少个字节。若进程给出的数据块太大,TCP 就很有必要把它分片,以便单独 IP 数据报形式发送每一个分片。同时,IP 层并不保证数据包一定被正确地传输给接收端,即 TCP 必须具有超时判断、重传数据、纠正错序、按序重装等功能。
一句话,TCP 必须提供多数用户所期望的可靠服务,也就是 IP 层没有提供的功能。
Q & A
- 请问,perror函数有什么用?
回答:将上一个函数(如setsockopt)的错误原因输出到标准设备。
- 请解释一下struct sockaddr_in这个结构体里有什么成员,各有什么用,每个成员占多少字节?
回答:sockaddr_in是系统封装的一个结构体,具体包含了成员变量:sin_family、sin_addr、sin_zero这个结构体被封装在ws2def.h中。sin_family表示传输的协议,字节数为2;sin_addr表示可访问的ip地址,字节数为4;sin_zore没特别含义,字节数为1。
- 请问,为什么每次循环前都要将buffer数组置零?
回答:在数据写满整个buf数组后,数据写入须从最开始写入,否则造成覆盖其他地址的数据。
- 请问,recvfrom函数有什么用,对它的最后一个参数需要做什么处理?如果不这么处理,会有什么后果?请用实验证明
回答:收取socket的数据包,将其保存在buf中,同时通过用户的网络通信协议设置相对应的长度,来设置为NULL。如果不指明该参数长度,就容易发生溢出。
- 请问,argc这个参数有什么作用?argv呢?argv[1]代表什么意义
回答:命令行参数有时用来启动一个程序的执行,其中第一个参数argc表示命令行参数的数目,程序自动统计,它是int类型的;二个参数argv是一个指向字符串的指针数组,由于参数的数目并没有内在的限制,所以argv指向这组参数值(从本质上来说是一个数组)的第一个元素,这些元素的每个都是指向一个参数文本的指针。指的是执行程序名后的第一个字符串。
- 请问,gethostbyname函数它的主要作用是什么?
回答:用域名或者主机名获取地址,操作系统提供的库函数。
- 请问,gethostbyname函数的返回值是什么 类型,请使用man查看一下,它有什么成员函数?
回答:类型是struct hostent(返回hostent结构体类型指针);*h_name表示主机名;**h_aliases表示主机所有的别名;h_addrtype表示主机IP地址的类型;h_length表示主机的IP地址长度;**h_add_list表示主机的IP地址,同时以网络字节序存储。
- 请问,fgets函数的作用是什么?
回答:从指定的流 stream 读取一行,并把它存储在str所指向的字符串内。当读取(n-1)个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
- 请问,对于sendto函数的最后一个参数,我们需要做什么处理?
回答:通过接收方的源地址设置为合适的长度,通常设置为【sizeof(serv_addr)】,否则容易造成溢出。
- 在wireshark里,如何过滤端口号为80的TCP包?
回答:过滤TCP协议端口号为80的包,tcp.port80;只过滤TCP协议源端口号为80的包,tcp.srcport80;过滤TCP协议目的端口号为80的包,tcp.dstport==80。
- 如果提示找不到arm-linux-gcc,应该怎么办?
回答:1.切换到超级命令:sudo -s
2.输入:export PATH=$PATH:/usr/local/arm/6.2.1/bin
3.再次编译:make zImage