TCP服务器程序
下面是一个简单的TCP服务器程序(server_v1.cpp),功能是将客户端发送的字符串转换成大写后返回:
//server_v1.cpp
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 80
#define SERV_PORT 8000
int main(void)
{
sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i;
ssize_t n;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr,sizeof(servaddr));
listen(listenfd, 20);
printf("Accepting connections ...\n");
while (true)
{
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd,
(struct sockaddr *)&cliaddr,
&cliaddr_len);
n = read(connfd, buf, MAXLINE);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET,
&cliaddr.sin_addr, str,sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
write(connfd, buf, n);
close(connfd);
}
}
相关API
socket
int socket(int family, int type, int protocol);
socket()
打开一个网络通讯端口,如果成功的话,就一个文件描述符,应用程序可以像读写文件一样用read
或write
在网络上收发数据,如果socket()
调用出错则返回-1。对于IPv4, family参数指定为AF_INET
。对于TCP协议, type参数为SOCK_STREAM
,表示面向流的传输协议。如果是UDP协议,则type参数指定为SOCK_DGRAM
,表示面向数据报的传输协议。protocol参数的介绍从略,指定为0即可
bind
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用bind
绑定一个固定的网络地址和端口号。 bind()
成功返回0,失败返回-1。bind()
的作用是将参数sockfd
和myaddr
绑定在一起,使sockfd
这个用于网络通讯的文件描述符监听myaddr
所描述的地址和端口号。各参数意义如下:
- myaddr:
struct sockaddr *
是一个通用指针类型 - addrlen: 由于myaddr参数实际上可以接受多种协议的
sockaddr
结构体,而它们的长度各不相同,所以需要第三个参数addrlen
指定结构体的长度
程序中对myaddr
参数是这样初始化的:
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
首先将整个结构体清零,然后设置地址类型AF_INET
,网络地址为INADDR_ANY
,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址,端口号为SERV_PORT
,代码中将其定义为8000
listen
int listen(int sockfd, int backlog);
典型的服务器程序可以同时服务于多个客户端,当有客户端发起连接时,服务器调用的accept()
返回并接受这个连接,如果有大量的客户端发起连接而服务器来不及处理,尚未accept的客户端就处于连接等待状态, listen()
声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接待状态,如果接收到更多的连接请求就忽略。listen()
成功返回0,失败返回-1
accept
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
服务器调用accept()时如果还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。各参数意义如下:
- cliaddr: cliaddr是一个传出参数,
accept()
返回时传出客户端的地址和端口号,如果给cliaddr参数传NULL,表示不关心客户端的地址。 - addrlen: addrlen参数传入后会被修改,传入时是调用者提供的缓冲区cliaddr的长度,以避免缓冲区溢出问题,传出时是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)