聊天服务器-编写TCP服务器

Posted by 周思进 on August 3, 2019

网络编程中涉及的各地址结构体及相关接口入参不够熟,常常需要通过查找来确认,本文通过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服务器就编写完成了。