下图是基于TCP协议的客户端/服务器程序的一般流程
服务器调用socket()、bind()、listen()
完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。
数据传输的过程:
建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是 由客户端主动发起请求,服务器被动处理请求,一问一答的方式。
因此,服务器从accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。
如果客户端没有更多的请求了,就调用close()关闭连接,就像写端关闭的管道一样, 服务器的read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。
注意:
任何一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了。
如果一方调用shutdown()则连接处于半关闭状态,仍可接收对方发来的数据。
通过最简单的客户端/服务器程序的实例:
server服务端代码:
server.c
的作用是从客户端读字符,然后将每个字符转换为大写并回送给客户端。
/* server.c */
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 80
#define SERV_PORT 8000
int main(void)
{
/*网络套接字地址结构体 服务端servaddr 客户端cliaddr*/
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd; //监听文件描述符、连接文件描述符
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];//#define INET_ADDRSTRLEN 16 /* for IPv4 dotted-decimal */
int i, n;
listenfd = socket(AF_INET, SOCK_STREAM, 0); //打开一个网络通讯端口,分配一个文件描述符listenfd
bzero(&servaddr, sizeof(servaddr)); //清空服务端套接字
servaddr.sin_family = AF_INET; //地址采用IPv4地址
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//地址从主机字节顺序转换成网络字节顺序
servaddr.sin_port = htons(SERV_PORT); //端口号从主机字节顺序转换成网络字节顺序
/*将文件描述符listenfd和服务器地址绑定在一起*/
bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
/*声明listenfd处于监听状态,并且最多允许有20个客户端处于待连接状态*/
listen(listenfd, 20);
printf("等待连接中...\n");
while(1){
cliaddr_len = sizeof(cliaddr);
/*connfd文件描述符用于和客户端通信,服务器调用accept()接受连接*/
connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len);
/*从connfd中读取数据*/
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; itoupper(buf[i]);
}
/*往connfd中写数据*/
write(connfd, buf, n);
close(connfd);
}
}
client客户端代码
client.c
的作用是从命令行参数中获得一个字符串发给服务器,然后接收服务器返回的 字符串并打印。
/* client.c */
#include
#include
#include
#include
#include
#include
#define MAXLINE 80
#define SERV_PORT 8000
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
char *str;
if(argc != 2){
fputs("args err\n", stderr);
exit(1);
}
str = argv[1];
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
/*连接*/
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
/*向sockfd写入字符串str*/
write(sockfd, str, strlen(str));
/*读取经过服务端处理的sockfd到buf中*/
n = read(sockfd, buf, MAXLINE);
printf("服务端应答:\n");
/*输出buf*/
write(STDOUT_FILENO, buf, n);
close(sockfd);
return 0;
}
编译执行及运行结果:
yu@ubuntu:~/Linux/212$ gcc -o client client.c
yu@ubuntu:~/Linux/212$ gcc -o server server.c
yu@ubuntu:~/Linux/212$ ls
client client.c server server.c
yu@ubuntu:~/Linux/212$ ./server
等待连接中...
received from 127.0.0.1 at PORT 41404
received from 127.0.0.1 at PORT 41405
received from 127.0.0.1 at PORT 41406
received from 127.0.0.1 at PORT 41407
另开一终端执行客户端程序:
yu@ubuntu:~/Linux/212$ ./client hey
服务端应答:
HEY
yu@ubuntu:~/Linux/212$ ./client howareyou
服务端应答:
HOWAREYOU
yu@ubuntu:~/Linux/212$ ./client 3q
服务端应答:
3Q
yu@ubuntu:~/Linux/212$ ./client bye
服务端应答:
BYE