网络编程中涉及的各地址结构体及相关接口入参不够熟,常常需要通过查找来确认,本文通过man手册来快速查找相关信息以编写一个简单的TCP服务器。
首先对TCP通信的整体流程要有所了解,两端通信首先需要知道目标主机是谁,这个可以通过IP地址来确定,而跟目标主机的哪个应用通信,则可以通过端口号来区分,而同一端口号可以被不同的协议所使用,所以还得明确通信使用的协议方式。
基本常见tcp服务器实现流程如下:
socket->bind->listen->accept
1、先创建一个套接字,确认要使用的地址族和协议方式
2、将该套接字与本地ip地址和端口号进行绑定
3、对该套接字设定最大允许的客户连接数
4、开启监听模式,等待客户端连接
确认流程之后,对其中涉及的接口通过man手册明确需要传递的入参。
通过man查看的时候,信息会比较多,对于编写一个简单可用的TCP服务器程序,基本看下当前用的着的,到时遇到其他问题再来查。
一、
int socket(int domain, int type, int protocol);
domain入参指定地址族,如ipv4通信,使用AF_INET;ipv6通信,则使用 AF_INET6。
type入参指定套接字类型,如tcp协议是SOCK_STREAM,udp协议是SOCK_DGRAM。
protocol入参默认使用0表示根据前面的domain和type选择适应的协议。
这样第一步创建套接字就算完成了。
二、
int bind(int sockfd,const struct sockaddr *addr,
socklen_t addrlen);
有了套接字之后,需要绑定ip地址和端口号,这里的重点在于入参addr,该指针对应的结构体在man手册中说明如下:
The rules used in name binding vary between address families. Consult the manual entries in Section 7 fordetailed information. For AF_INET seeip(7), for AF_INET6 see ipv6(7), for AF_UNIX see unix(7), for AF_APPLETALK see ddp(7), forAF_PACKET seepacket(7), for AF_X25 see x25(7) and for AF_NETLINK see netlink(7).
The actual structure passed for the addr argument will depend on the addressfamily. The sockaddr structure isdefined assomething like:
struct sockaddr { sa_family_t sa_family; char sa_data[14]; }
The only purpose ofthis structure is to cast the structure pointer passed in addr in order to avoid compiler warnings.
其结构体是为解决编译问题对入参做了强制转换,而实际传递的地址根据不同的地址族有所不同,比如AF_INET可以通过 ip(7)手册查看。
通过ip(7)手册查看到,ipv4的地址格式如下:
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
其中sin_family指定使用AF_INET。
sin_port是需要绑定的端口号,需要注意是通过网络字节序存储。
sin_addr是一个结构体,用于存储所要绑定的ipv4地址,也需要通过网络字节存储,如果想绑定当前主机的任何一个地址,即不明确指定,则可以使用INADDR_ANY进行赋值(其实就是将s_addr赋值成0)。
另外手册里有提到对于已建立连接的tcp套接字在关闭之后,需要等待一段时间之后才可用,除非设置了SO_REUSEADDR标志。
通过对这些了解后,bind接口也就基本完成了。
在看bind帮助手册的时候,有提到socket(7)可以查看更多关于sockets的信息,看了下该帮助手册基本把常用的接口及大致用途都说明了下,很有参考意义,还有选项设置。
三、
int listen(int sockfd, int backlog);
这个接口相对简单,表示的是等待连接队列中限制的最大连接数。
四、
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept接口用于等待客户端连接,客户端连接后,其客户端信息(ip和端口)就会存储在入参addr里,addrlen是一个值-结果入参,如果不需要获取这些信息,可以置成NULL。该接口调用成功返回的是一个文件描述符,可用于后续的读写通信。
需要注意的是如果socket不是非阻塞的,那么在没有客户端连接的情况下,accept会阻塞调用直到有一个连接过来。
这样一个基本的tcp服务器就编写完成了。