UNIX网络编程一.TCP客户/服务器程序

1. 代码

服务端代码

#include	"unp.h"

int
main(int argc, char **argv)
{
	int					listenfd, connfd;
	pid_t				childpid;
	socklen_t			clilen;
	struct sockaddr_in	cliaddr, servaddr;

	listenfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  //((in_addr_t) 0x00000000) ==> uint32_t
	servaddr.sin_port        = htons(SERV_PORT);   //9877

	Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

	Listen(listenfd, LISTENQ);						//LISTENQ: 1024

	for ( ; ; ) {
		clilen = sizeof(cliaddr);
		connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);

		if ( (childpid = Fork()) == 0) {	/* child process */
			Close(listenfd);	/* close listening socket */
			str_echo(connfd);	/* process the request */
			exit(0);
		}
		Close(connfd);			/* parent closes connected socket */
	}
}


void
str_echo(int sockfd)
{
	ssize_t		n;
	char		buf[MAXLINE];

again:
	while ( (n = read(sockfd, buf, MAXLINE)) > 0)		//从套接字读取数据,读的数据有效
														//客户若关闭连接,收到客户发送的FIN,返回0
		Writen(sockfd, buf, n);							//回射给客户

	if (n < 0 && errno == EINTR)						//阻塞假错EINTR
		goto again;
	else if (n < 0)										//真错
		err_sys("str_echo: read error");
}

客户端代码

#include	"unp.h"

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(SERV_PORT);				//服务端服务端口9877
	Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);	//命令行传入服务端地址

	Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));

	str_cli(stdin, sockfd);		/* do it all */

	exit(0);
}


void
str_cli(FILE *fp, int sockfd)
{
	char	sendline[MAXLINE], recvline[MAXLINE];

	while (Fgets(sendline, MAXLINE, fp) != NULL) {	//从标准输入读取有效时

		Writen(sockfd, sendline, strlen(sendline));//写给服务器

		if (Readline(sockfd, recvline, MAXLINE) == 0)//阻塞,读取从服务器回射回来的
			err_quit("str_cli: server terminated prematurely");

		Fputs(recvline, stdout);				//打印到标准输出
	}
}

服务端代码缺陷

  1. 子进程结束,父进程没有给子进程收尸,signal函数捕获SIGCHLD信号,sig_child函数是具体的实现,具体实现要用waitpid而不是wait(wait缺点:若同时多个子进程终止,会信号丢失,只处理一次。waitpid可以指定我们想等待的进程ID)
  2. accept属于慢系统调用,内核会使accept返回一个EINTR错误(被中段的系统调用)。要正确处理这种错误

针对缺陷改进

#include	"unp.h"

int
main(int argc, char **argv)
{
	int					listenfd, connfd;
	pid_t				childpid;
	socklen_t			clilen;
	struct sockaddr_in	cliaddr, servaddr;
	void				sig_chld(int);

	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, (SA *) &servaddr, sizeof(servaddr));

	Listen(listenfd, LISTENQ);

	Signal(SIGCHLD, sig_chld);	/* !!!  1. must call waitpid() !!!*/

	for ( ; ; ) {
		clilen = sizeof(cliaddr);
		if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {
			if (errno == EINTR)
				continue;		/* !!! 2. back to for() !!!*/
			else
				err_sys("accept error");
		}

		if ( (childpid = Fork()) == 0) {	/* child process */
			Close(listenfd);	/* close listening socket */
			str_echo(connfd);	/* process the request */
			exit(0);
		}
		Close(connfd);			/* parent closes connected socket */
	}
}

2. accept返回前连接中止

在这里插入图片描述
如果我们在调用accept函数返回之前, 该客户端TCP发送了一个RST(复位)。在服务器中, 表现为该连接仍在TCP队列中, 等待服务器进程调用accept的时候RST到达。此时返回的套接字是一个已连接,但是却有接受了RST的套接字。POSIX规定返回的errno为ECONNABORTED,服务器可以忽略他,再次调用accept就行。
TCP异常处理(accept返回前连接中止)与SO_LINGER选项

3. 服务器进程终止

杀死服务端的对接客户端的子进程ID,SIGCHILD被父进程捕获,并正确处理。客户端阻塞在fgets准备读取标准输入的数据,所以服务端发送的FIN被接收却看不到/客户端继续向服务端发送数据,所以服务端会响应一个RST(已经终止),然而客户端在向服务端发送完数据后立即调用readline准备读取回射的字符串,但是readline会立即返回0(之前的FIN此时看到了),于是有了出错信息 "str_cli: server terminated prematurely"

4. SIGPIPE信号

向某个已经收到RST的套接字执行写操作时,内核向该进程发送一个SIGPIPE信号。

客户端测试代码

#include	"unp.h"

void
str_cli(FILE *fp, int sockfd)
{
	char	sendline[MAXLINE], recvline[MAXLINE];
	while (Fgets(sendline, MAXLINE, fp) != NULL) {//服务器进程终止发送FIN,客户端未被告知已收到FIN,仍继续发
		//一句话分2次发,中间sleep 1s
		Writen(sockfd, sendline, 1);		//发完这句话会让服务端发送RST
		sleep(1);
		Writen(sockfd, sendline+1, strlen(sendline)-1);//引发SIGPIPE信号
		if (Readline(sockfd, recvline, MAXLINE) == 0)
			err_quit("str_cli: server terminated prematurely");
		Fputs(recvline, stdout);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值