这里我们用前面所介绍的基础知识编写一个完整的TCP客户服务器程序示例。我们要实现的是一个回射服务器:
1)客户从标准输入读入一行文本,并发送给服务器
2)服务器从网络输入读入这行文本,并回射给客户
3)客户接受到回射文本后,将其显示在标准输出上。
tcp服务器源码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#define SERV_PORT 5678
#define MAX_LINE 1024
void str_echo(int sockfd)
{
ssize_t n;
char buf[MAX_LINE] = {0};
while( (n = read(sockfd, buf, MAX_LINE)) > 0)
write(sockfd, buf, n);
if( n < 0)
printf("read error.\n");
}
int main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t cli_len;
struct sockaddr_in cliaddr,servaddr;
cli_len = sizeof(cliaddr);
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd < 0)
{
printf("create socket error!\n");
exit(1);
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
printf("bind error!\n");
exit(1);
}
listen(listenfd, 5);
for(;;)
{
connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cli_len);
if( (childpid = fork()) == 0)/*child process*/
{
close(listenfd);
str_echo(connfd);
exit(0);
}
close(connfd); /*parent close connected socket*/
}
return 0;
}
|
tcp客户端源码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#define SERV_PORT 5678
#define MAX_LINE 1024
void str_cli(FILE *fp, int sockfd)
{
char sendline[MAX_LINE], recvline[MAX_LINE];
int n;
while( fgets(sendline, MAX_LINE, fp) != NULL)
{
write(sockfd, sendline, strlen(sendline));
if( (n = read(sockfd, recvline, MAX_LINE)) == 0)
exit(0);
recvline[n]='\0';
fputs(recvline, stdout);
}
}
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
printf("create socket error!\n");
exit(1);
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
str_cli(stdin, sockfd);
return 0;
}
|
源码中需要注意的地方就是tcp服务器代码中的一段:
if( (childpid = fork()) == 0)/*child process*/
{
close(listenfd);
str_echo(connfd);
exit(0);
}
这里在子进程中,首先将listenfd关闭,因为fork函数后,子进程会获得父进程资源的副本,而子进程中并不需要listenfd,直接关闭免的出麻烦。在str_echo调用完成后,并没有使用close(connfd),而是直接调用exit函数。这里我们看下exit函数究竟会做什么。
exit调用之后,会调用退出处理函数,这里就是一些对资源的释放,包括已打开的套接字。
直接在linux上编译运行服务器端:
服务端启动后会在5678端口上监听:
然后启动客户端:
并在输入端输入hello,world<回车>后,会显示hello,world,此时我们再看下网络连接状况:
可以看出,服务端在接收到一个连接请求后,fork了一个子进程进行处理,主进程仍然在监听。同时,客户端和服务器端已经建立了连接。
当我们在客户端按下ctrl+d退出之后,再看下网络连接:
服务器的主进程仍然在监听,而客户端的socket套接字的tcp状态处于time_wait状态,至于为什么会到time_wait状态可以看下我以前的博文。此时,服务器的子进程已经退出,我们可以看下进程的状态:
stat中Z表示僵死。可以看出tcp主进程阻塞与accept函数,子进程虽然退出,但是处于僵死状态。这是因为,子进程退出时,会向父进程发送一个SIGCHLD的信号,一般情况下,父进程接收到该信号后,负责对子进程进行清理工作。但是我们在父进程中并没有处理该信号,于是,子进程便处于僵死状态,所占用的资源并没释放。其实,这段代码并未对异常进程处理,所以健壮性方面很差。
阅读(788) | 评论(0) | 转发(0) |