socket编程——TCP server编程框架分析

    在上一篇文章 《TCP套接字编程--常用函数小结》我们分析了套接字编程的一些常用函数,本文基于这些文章,结合一个TCP server服务程序进一步分析一下socket编程。程序框架如下:

#include 
#include 
#include 
#include 
#include 		//bzero...func
#include 		//honl\hons..sockaddr_in...

#define     MAXLINE		1024

#define     PORT 		5000
#define     BACKLOG		10

ssize_t
writen(int fd, const void *vptr, size_t n)
{
	size_t nleft;
	ssize_t nwritten;
	const char *ptr;

	ptr = vptr;
	nleft = n;

	while( nleft > 0){
		if((nwritten = write(fd, ptr, nleft)) <= 0){
			if( nwritten < 0 && errno == EINTR)
				nwritten = 0;
			else
				return (-1);
		}
		nleft -= nwritten;
		ptr += nwritten;
	}
	return (n);
}


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

again:
	while( (n = read(sockfd, buf, MAXLINE)) > 0)
		writen(sockfd, buf, n);

	if(n < 0 && errno == EINTR)
		goto again;
	else if(n < 0)
		err_sys("str_echo: read error");
}

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);
	servaddr.sin_port = htons(PORT);

	bind(listenfd, (sockaddr *)&servaddr, sizeof(servaddr));

	listen(listenfd, BACKLOG);

	while( 1 ){
		clilen = sizeof(cliaddr);
		connfd = accept(listenfd, (sockaddr *)&cliaddr, &clilen);

		if( (childpid = fork()) == 0){ // child process
			close(listenfd);
			str_echo(connfd);
			exit( 0 );
		}
		close( connfd );
	}
}

  这个程序是Unix网络编程中的一个典型案例—— TCP server 回射服务。首先我们来梳理下程序逻辑:

1)新建socket数据结构。

2)对socket数据结构进行赋值,包括协议类型、客户端IP 地址,端口号等。

3)将上述的socket结构绑定给套接字,客户端非必须,因为内核会自己会默认绑定分配的。

4)调用listen函数启动监听。新建的套接字默认是主动套接字,也就是客户端套接字,通过listen函数可以将该套接字设置为被动套接字, 也就是被动监听。套接字排队最大连接个数,这里定义最大连接个数为10。注意:listen函数不要在主循环中。内核里已经循环监听了。

5)进入主循环,在主循环中调用accept,该函数从完成连接的客户端队列头中返回新连接客户端,如果没有客户端,则阻塞,进程被睡眠。

6)一旦accept返回,则表示有 新的客户端连接,使用fork生成一个子进程,在fork子进程中“关闭“监听套接字,因为子进程会复制监听套接字,而子进程中不需要监听套接字,所以可以直接用close关闭,确切的说,此处的关闭 不是 真的关闭,而是将监听套接字引用计数减1。然后子进程就可以与客户端进行数据通信了。

7)父进程接着执行,父进程也“关闭”客户端套接字,因为不需要,同样的道理,将客户套接字引用计数减1.

8)理论上还应该添加waitpid函数, 等待子进程退出,清理子进程的 资源,否则子进程将会成为 僵尸进程。这里没有体现。

9)父进程再次循环,执行accept,阻塞等待新的客户端连接。

你可能感兴趣的:(TCP/IP)