《UNPv1》:来射字符串的服务器/客户端(多进程TCP版)

《UNPv1》:回射字符串的服务器/客户端(多进程TCP版)

    《Unix网络编程卷1:套接字联网API》这本书附带了许多短小精美的小程序,我在阅读此书的时候,将书上的代码按照自己的理解重写了一遍(大部分是抄书上的),加深一下自己的理解(纯看书太困了,呵呵)。此例子在Ubuntu10.04上测试通过。

    PS:程序里使用了包裹函数(首字母是大写的函数)和常量(所有字母都是大写的常量)的声明和定义在my_unp_v1.hmy_unp_v1.c中,地址:http://blog.csdn.net/aaa20090987/article/details/8096701


    程序简介:这一对例子演示了多进程服务器和客户端使用TCP协议传输数据的基本原理和流程。当一个客户端连接上服务器时,服务器就产生一个子进程来与客户端进行通信。


服务端:

#include "my_unp_v1.h"

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

again:
	//从套接字中读取数据,写到buffer中去
	//再将buffer中的数据写到套接字中去
	while( (n=read(sockfd, buf, MAXLINE)) > 0 )
		Writen(sockfd, buf, n);

	//由于信号中断,没写或读成功任何数据
	if( n<0 && errno==EINTR )
		goto again;
	else if( n < 0 )
		error_quit("str_echo: read error");
}

//捕获并处理子进程的SIGCHLD信号
void sig_child(int signo)
{
	pid_t pid;
	int stat;
	while( (pid=waitpid(-1, &stat, WNOHANG)) > 0 )
		printf("child %d terminated\n", pid);
	return;
}

int main(void)
{
	int listenfd, connfd;
	pid_t childpid;
	socklen_t clilen;
	char buff[MAXLINE];
	struct sockaddr_in cliaddr, servaddr;

	//创建用于TCP协议的套接字 
	listenfd = Socket(AF_INET, SOCK_STREAM, 0);
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);

	//把socket和socket地址结构联系起来  
	Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));
	//开始监听LISTENQ端口
	Listen(listenfd, LISTENQ);

	//处理SIGCHLD信号,防止子进程变成僵死进程
	Signal(SIGCHLD, sig_child);

	while(1)
	{
		clilen = sizeof(cliaddr);

		//接受一个来自客户端的连接  
		//如果没有连接请求,就使程序睡眠,直到有连接请求--这是accept函数的特性  
		//accept函数返回一个描述符,这个socket(connfd)用于同连接到的客户的通信 
		connfd = accept(listenfd, (SA*)&cliaddr, &clilen);

		if( connfd < 0 )
		{
			//accetp()是慢系统调用,在信号产生时会中断其调用, 
			//并将errno变量设置为EINTR,此时应重新调用
			if( errno == EINTR )
				continue;
			else
				error_quit("accept error");
		}

		//产生一个子进程,让它处理与(某个客户端的)通信
		childpid = Fork(); 
		if( childpid == 0 )
		{
			Close(listenfd);
			str_echo(connfd);
			return 0;
		}
		//输出客户端的IP地址与端口号,还有处理(子)进程的PID
		printf("connection from %s, port %d. process with clild %d\n",  
			Inet_ntop(AF_INET, (void*)&cliaddr.sin_addr, buff, sizeof(buff)),  
			ntohs(cliaddr.sin_port), childpid); 
		//与客户端的通信由子进程来处理,所以关闭此套接字
		Close(connfd);
	}
	return 0;
}

客户端:

#include "my_unp_v1.h"

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 )
			error_quit("str_cli: server terminated prematurely");
		Fputs(recvline, stdout);
	}
}

int main(int argc, char **argv)
{
	int sockfd;
	struct sockaddr_in servaddr;
	if( argc != 2 )
		error_quit("usage: client <IPAddress>");

	//创建用于TCP协议的套接字 
	sockfd = Socket(AF_INET, SOCK_STREAM, 0);
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT);

	//将程序的参数1(argv[1])转换成套接字地址结构
	Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
	////向服务器发起连接,连接成功后client_socket代表了客户机和服务器的一个socket连接 
	Connect(sockfd, (SA*)&servaddr, sizeof(servaddr));

	str_cli(stdin, sockfd);
	return 0;
}


运行示例(红色字体的为输入)

服务器端:

qch@ubuntu:~/code$ gcc my_unp_v1.c server.c -o server
qch@ubuntu:~/code$ ./server 
connection from 127.0.0.1, port 47538. process with clild 11205
child 11205 terminated

客户端:

qch@ubuntu:~/code$ gcc my_unp_v1.c client.c -o client
qch@ubuntu:~/code$ ./client 127.0.0.1
ABCD
ABCD
Ctrl+D