在上一篇文章 《TCP套接字编程--常用函数小结》我们分析了套接字编程的一些常用函数,本文基于这些文章,结合一个TCP server服务程序进一步分析一下socket编程。程序框架如下:
#include <sys/socket.h>
#include <stdio.h>
#include <stdin.h>
#include <unistd.h>
#include <strings.h> //bzero...func
#include <netinet/in.h> //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,阻塞等待新的客户端连接。