一、TCP协议的基础编程模型
TCP是面向连接的通信协议,采用客户机-服务器模式。套接字的全部工作流程如下所述:
1.服务器启动进程,调用Socket创建一个基于TCP协议的流套接字描述符。
2.其次,服务进程调用bind命名套接字,将套接字描述符绑定到本地地址和本地端口上,至此Socket的半相关描述---{协议,本地地址,本地端口}---完成。
3.再次,服务器端调用listen,开始侦听客户端的Socket连接请求。
4.接下来,客户端创建套接字描述符,并且调用connect向服务端提交连接请求。服务器端接收到客户端连接请求后,调用accept接受,并创建一个新的套接字描述符与客户端建立连接,然后原套接字描述符继续侦听客户端的连接请求。
5.客户端与服务器端的新套接字进行数据传送,调用write或send向对方发送数据,调用read或recv接收数据。
6.在数据交流完毕后,双方调用close或者shutdown半闭套接字。
二、UNIX基于TCP的通信相关函数
1.Socket的创建---系统调用Socket
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
返回值:成功返回创建的套接字描述符,否则返回-1并置errno。
说明:函数socket创建一个Socket描述符。
domain 指定发送通信的域,即套接字的协议族。有AF_UNIX(本地主机通信)和AF_INET(Internet地址IPV4协议)。
type 指定通信类型,有SOCK_STREAM,SOCK_DGRAM和SOCK_RAW。
Protocol指定了该套接字描述符上的一个特殊的协议书,比如TCP、UDP等,一般设置为0。
2.Socket的命名---系统调用bind
#include<sys/types.h>
#include<sys/socket.h>
int bind(int s, const struct sockaddr *name, int namelen);
返回值:成功返回0,否则返回-1并置errno。
函数bind命名一个套接字,它为该套接字描述符分配一个半相关属性。
s 调用socket创建的套接字。
指针name指向通用套接字的协议地址结构,包括协议、地址和端口信息等。
namelen 指定该协议地址结构长度,一般定义为sizeof(sockaddr_in)。
关于套接字地址属性结构:
struct sockaddr{
u_short sa_family; /* 协议族 */
char sa_data[14]; /* 最多14字节的协议地址 */
};
不同的协议有不同的地址描述方式,为了便于编码处理,每种协议族都定义了自己的套接字地址属性结构,比如协议族AF_INET使用结构sockaddr_in描述套接字地址信息,该结构定义在头文件<netinet/in.h>中。
struct sockaddr_in{
short sin_family; /* 16位的地址协议族(AF_INET)*/
u_short sin_port; /* 16位的端口地址 */
struct in_addr sin_addr; /* 32位的IP地址 */
char sin_zero[8]; /* 预留 */
};
struct in_addr{
u_long s_addr;
};
预留成员作用是使结构sockaddr_in与结构sockaddr的长度相同。
IP地址转换---完成IP地址的字符串格式与32位4字节整型之间的转换。
unsigned long inet_addr(char *ptr);
int inet_aton(char *ptr, stuct in_addr *addrptr);
char *inet_ntoa(struct in_addr inaddr);
字节顺序转换---实现主机字节顺序与网络字节顺序之间转换
#include<sys/types.h>
#include<netinet/in.h>
u_short htons(u_short hostshort); /*将16位整数由主机字节顺序转换成网络字节顺序*/
u_long htonl(u_long hostlong); /*将32位整数由主机字节顺序转换成网络字节顺序*/ u_short ntohs(u_short netshort); /*将16位整数由网络字节顺序转换成主机字节顺序*/
u_long ntohl(u_long netlong); /*将32位整数由网络字节顺序转换成主机字节顺序*/
例:命名套接字描述符s的协议为AF_INET,地址为“127.0.0.1”,端口号为1000。
struct sockaddr_in sockaddr1; /*申请地址结构空间*/
memset(&sockaddr1, 0 , sizeof(sockaddr1)); /*清空空间*/
sockaddr1.sin_family = AF_INET;
sockaddr1.sin_addr.s_addr = inet_addr(“127.0.0.1”); /*IP地址转换为整型赋值*/
sockaddr1.sin_port = htons(1000); /*端口号 主机字节顺序转换为网络字节顺序*/
如需系统自动指定套接字的IP地址,则
sockaddr1.sin_addr.s_addr = htonl(INADDR_ANY); /*自动分配地址*/
3.Socket的侦听---系统调用listen
#include<sys/types.h>
#include<sys/socket.h>
int listen(int s, int backlog);
返回值:成功返回0,否则返回-1并置errno。
TCP服务器端必须调用listen函数才能使套接字进入侦听状态。
s 调用socket创建、bind命名的套接字。
backlog 确定套接字s接收的最大数目,如果到达最大的连接数,服务器端将向客户端发出ECONNRERUSED错误。