TCP客户与服务器进程之间发生的重大事件时间表
TCP服务器
socket() --- bind() --- listen() --- accept() --- read() --- write --- read() --- close
TCP客户
socket() --- connect() --- write() --- read() --- close()
套接字函数简介
int socket(int family, int type, int protocol);
指定要用的通信协议类型
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
客户用connect()来建立与TCP服务器的连接
int bind(int sockfd, const struct sockaddr *myaddr, scoklen_t addrlen);
把一个本地协议地址赋予一个套接字
int listen(int sockfd, int backlog);
设置套接字为被动套接字,指示内核接收向该套接字的连接请求
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
由TCP服务器调用, 用来从已完成连接队列队头返回下一个已完成连接
pid_t fork(void);
产生子进程
int close(int sockfd);
关闭套接字, 并终止TCP连接
典型的并发服务器程序轮廓
pid_t pid;
int listenfd, connfd;
listenfd=Socket(...);
Bind(listenfd, ...);
Listen(listenfd,LISTENQ);
for(;;){
connfd=Accept(listenfd, ...);
if((pid=Fork())==0){//如果是子进程
Close(listenfd);//子进程关闭其监听套接字
doit(connfd);//处理需求
Close(connfd);//关闭子进程的已连接套接字
exit(0);//子进程终止
}
Close(connfd);//父进程关闭已连接套接字
}
TCP echo服务器程序
#include "unp.h"
#include <time.h>
void srv_echo(int sockfd)
{
ssize_t n;
char buf[1000];
again:
while((n=read(sockfd,buf,1000))>0)
Writen(sockfd,buf,n);
if(n<0 && errno==EINTR)
goto again;
else if(n<0)
err_sys("srv_echo: read error");
}
int
main(int argc,char ** argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
char buff[1000];
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(1234);
Bind(listenfd, (SA*) &servaddr, sizeof(servaddr));
Listen(listenfd, 100);
for(;;){
clilen=sizeof(cliaddr);
connfd=Accept(listenfd, (SA*) &cliaddr, &clilen);
printf("connection from %s , port %d\n",
inet_ntop(AF_INET,&cliaddr.sin_addr, buff, sizeof(buff)),
ntohs(cliaddr.sin_port));
if((childpid=Fork())==0){
Close(listenfd);
srv_echo(connfd);
exit(0);
}
Close(connfd);
}
}
TCP echo客户端程序
#include "unp.h"
void cli_echo(FILE *fp, int sockfd)
{
char sendline[1000], recvline[1000];
while(Fgets(sendline,1000, fp)!=NULL){
Writen(sockfd, sendline, strlen(sendline));
if(Readline(sockfd, recvline, 1000)==0)
err_quit("cli_echo: server terminated prematurely");
Fputs(recvline, stdout);
}
}
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if(argc!=2)
err_quit("usage: tcpcli <IPaddress>");
sockfd=Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(1234);
Inet_pton(AF_INET,argv[1], &servaddr.sin_addr);
Connect(sockfd, (SA*) &servaddr, sizeof(servaddr));
cli_echo(stdin,sockfd);
exit(0);
}
这一章的API讲解非常有逻辑性, 讲解的过程可以看到这样设计的合理性
4.5 listen函数
当一个客户的SYN到达, 若服务器的相应监听套接字维护的队列已经满了, TCP就会忽略该分节, 也就是不发送RST. (这里的TCP指的是实现TCP的内核)
之所以这样设计有两点理由:
(1)这种情况是暂时的, TCP的正常重传机制会处理这个问题
(2)客户无法区分这个RST意思是 "该端口没有服务器在监听" 还是 "该端口有服务器在监听, 不过队列已满"