前言:由于第五章主要介绍了TCP和UDP协议以及两者的包头的字段以及相应的功能,这里就不介绍了,对着字段看功能就好了,后续开始学习第六章
1、Socket
Socket实质上就是提供了通信的端点,每个socket都用一个半相关描述{协议,本地地址,本地端口},完整的socket描述{协议,本地地址,本地端口,远程地址,远程端口}
可以这样解释:套接字是通过标准的UNIX文件描述符和其他的程序通讯的一个方法。
2、Socket的三种类型
1)流式套接字(SOCK_STREAM)
提供可靠、面向连接的通讯流,流式套接字使用了TCP协议,保证了数据传输的正确性。
2)数据报套接字(SOCK_DGRAM)
定义了一种无连接的服务,数据通过相互独立的报文进行传输,无序且不保证可靠、无差错。使用的是UDP协议,为什么无连接?由于UPD不像TCP那样维护一个打开的连接,只需要把数据打包,把远程的IP贴上,然后把这个包发出去即可,不需要建立连接。
既然数据会丢失,那如何保证程序正常工作呢?其实每个使用UDP的程序都要有自己的对数据进行确认的协议,比如TFTP协议定义了每一个发出去的数据包,远程在接收到之后都要反馈一个数据告诉本地程序已经收到,如果在5秒内未得到回应,就重发ACK信号。
3)原始套接字
用于协议开发,可进行底层操作,一般不涉及
3、套接字的基本结构
sockaddr用来存储套接字地址
struct sockaddr { unsigned short sa_family; /* address族, AF_xxx */ char sa_data[14]; /* 14 bytes的协议地址 */ };
为了处理sockaddr,建立另一个结构sockaddr_in
/*因特网地址*/ struct in_addr { unsigned long s_addr; }; struct sockaddr_in { short int sin_family; /* Internet地址族 */ unsigned short int sin_port; /* 端口号 */ struct in_addr sin_addr; /* Internet地址 */ unsigned char sin_zero[8]; /* 添0(和struct sockaddr一样大小)*/ };
有一点很重要,就是一个指向struct sockaddr_in的指针可以声明指向一个sturct sockaddr 的结构。所以虽然socket() 函数需要一个structaddr * ,你也可以给他一个sockaddr_in * 。注意在structsockaddr_in 中,sin_family 相当于在 struct sockaddr 中的sa_family,需要设成“AF_INET”。最后一定要保证sin_port 和sin_addr 必须是网络字节顺序。这是因为sin_addr和sin_port时从IP和UDP协议层取出来的数据,而在IP和UDP协议层,是直接和网络相关的,所以必须用网络字节序 ,而sin_famliy只是内核用来判断struct sockaddr_in是存储什么类型数据,并且sin_famliy也不会发送到网络上,可以用主机字节序保存。
4、常用的系统调用函数,如socket(),bind(),listen(),accept(),send(),recv()各自的参数可以具体参考手册,用man (send/socket)查看,下面给个流式套接字例子介绍一下,服务器端代码,功能就是给远程客户端发送字符串"Hello,World"
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> /* 服务器要监听的本地端口 */ #define MYPORT 4000 /* 能够同时接受多少没有accept 的连接 */ #define BACKLOG 10 main() { /* 在sock_fd 上进行监听,new_fd 接受新的连接 */ int sock_fd, new_fd ; /* 自己的地址信息 */ struct sockaddr_in my_addr; /* 连接者的地址信息*/ struct sockaddr_in their_addr; int sin_size; /* 这里就是我们一直强调的错误检查.如果调用socket() 出错,则返回 */ if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { /* 输出错误提示并退出 */ perror(“socket”); exit(1); } /* 主机字节顺序 */ my_addr.sin_family = AF_INET; /* 网络字节顺序,短整型 */ my_addr.sin_port = htons(MYPORT); /* 将运行程序机器的IP 填充入s_addr */ my_addr.sin_addr.s_addr = INADDR_ANY; /* 将此结构的其余空间清零 */ bzero(&(my_addr.sin_zero), 8); /* 这里是我们一直强调的错误检查!! */ if (bind(sockfd, (struct sockaddr *)&my_addr,sizeof(struct sockaddr)) == -1) { /* 如果调用bind()失败,则给出错误提示,退出 */ perror(“bind”); exit(1); } /* 这里是我们一直强调的错误检查!! */ if (listen(sockfd, BACKLOG) == -1) { /* 如果调用listen 失败,则给出错误提示,退出 */ perror(“listen”); exit(1); } while(1) { /* 这里是主accept()循环 */ sin_size = sizeof(struct sockaddr_in); /* 这里是我们一直强调的错误检查!! */ if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) { /* 如果调用accept()出现错误,则给出错误提示,进入下一个循环 */ perror(“accept”); continue; } /* 服务器给出出现连接的信息 */ printf(“server: got connection from %s ”, inet_ntoa(their_addr.sin_addr)); /* 这里将建立一个子进程来和刚刚建立的套接字进行通讯 */ if (!fork()) { /* 这里是子进程 */ /* 这里就是我们说的错误检查! */ if (send(new_fd, “Hello, world! ”, 14, 0) == -1) { /* 如果错误,则给出错误提示,然后关闭这个新连接,退出 */ perror(“send”); close(new_fd); exit(0); } /* 关闭new_fd 代表的这个套接字连接 */ close(new_fd); } } /* 等待所有的子进程都退出 */ while(waitpid(-1,NULL,WNOHANG) > 0); }
对应的客户端的程序代码:
#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 PORT 4000 /* 我们一次所能够接收的最大字节数 */ #define MAXDATASIZE 100 int main(int argc, char *argv[]) { /* 套接字描述符 */ int sockfd, numbytes; char buf[MAXDATASIZE]; struct hostent *he; /* 连接者的主机信息 */ struct sockaddr_in their_addr; /* 检查参数信息 */ if (argc != 2) { /* 如果没有参数,则给出使用方法后退出 */ fprintf(stderr,“usage: client hostname ”); exit(1); } /* 取得主机信息 */ if ((he=gethostbyname(argv[1])) == NULL) { /* 如果gethostbyname()发生错误,则显示错误信息并退出 */ herror(“gethostbyname”); exit(1); } if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { /* 如果socket()调用出现错误则显示错误信息并退出 */ perror(“socket”); exit(1); } /* 主机字节顺序 */ their_addr.sin_family = AF_INET; /* 网络字节顺序,短整型 */ their_addr.sin_port = htons(PORT); their_addr.sin_addr = *((struct in_addr *)he->h_addr); /* 将结构剩下的部分清零*/ bzero(&(their_addr.sin_zero), 8); if(connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) { /* 如果connect()建立连接错误,则显示出错误信息,退出 */ perror(“connect”); exit(1); } if((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) { /* 如果接收数据错误,则显示错误信息并退出 */ perror(“recv”); exit(1); } buf[numbytes] = ‘ ’; printf(“Received: %s”,buf); close(sockfd); return 0; }