《UNIX网络编程》TCP网络编程基础(1)
2015-12-29 14:54
656 查看
最近发现只看书不打代码真的不行,所以还是决定自己把代码敲一遍,加深印象!
客户端从标准输入获得字符串,发送给服务器;
服务器统计收到的字符串长度,将结果返回给客户端;
客户端显示服务器返回的结果。
清除中间文件及可执行文件:
htons()函数:将一个16位数从主机字节顺序转换成网络字节顺序。
inet_pton()函数:将“点分十进制” -> “二进制整数”,同时适用于IPv4和IPv6。
其中:
p: presentation 表达格式 ASCII串;
n: numeric 数值格式 二进制
在 bind 和 accept 等函数中,需要将IPv4的地址结构sockaddr_in转换成通用地址结构sockaddr。
我们使用fork产生子进程来处理请求,父进程对子进程的结束进行处理,否则会产生僵死进程。处理的信号为SIGCHLD,注意处理使用waitpid而不是wait,如果只调用一次wait,由于信号不排队,所以当有多个子进程同时死亡,发送给父进程的多个SIGCHLD只会处理1次,如果循环调用wait,在还有未结束子进程的情况下,父进程将阻塞在wait上。
当服务器已经关闭,如果客户端试图向套接字写入数据,则会产生一个SIGPIPE信号,此时将造成程序的非正常退出。我们可以捕捉该信号,并进行一些善后工作。
服务器fork子进程之后,父进程应该关闭连接套接字,否则当有其他连接到来,将可能耗尽所有的套接字描述符。另外,子进程应关闭监听套接字。
实验内容:
服务器和客户端通过tcp通信;客户端从标准输入获得字符串,发送给服务器;
服务器统计收到的字符串长度,将结果返回给客户端;
客户端显示服务器返回的结果。
源代码:
/* * tcp_server.c */ #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <linux/in.h> #include <string.h> #include <signal.h> #define PORT 8888 #define LISTENQ 2 #define BUFSIZE 1024 typedef void (*sighandler_t)(int); /*向信号signum注册一个void (*sighandler_t)(int)类型 *的函数,函数句柄为handler *使用typedef简化 */ sighandler_t signal(int signum, sighandler_t handler); /*处理子进程的终止信号,防止僵死进程*/ void sig_chld(int sign); int main (int argc, char **argv) { struct sockaddr_in cliaddr, servaddr; int listenfd, connfd; pid_t childpid; socklen_t clilen; signal(SIGCHLD, sig_chld); if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) //监听套接字 { printf("socket error\n"); return -1; } bzero(&servaddr, sizeof(servaddr)); //清零 servaddr.sin_family = AF_INET; /*地址和端口都要转成网络字节序*/ servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //本地地址 servaddr.sin_port = htons(PORT); /*绑定地址结构到套接字描述符*/ if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) { printf("bind error\n"); return -1; } /*监听, LISTENQ为监听队列的长度*/ if (listen(listenfd, LISTENQ) < 0) { printf("listen error\n"); return -1; } /*主循环*/ for( ; ; ) { clilen = sizeof(cliaddr); /*接收客户端连接*/ connfd = accept(listenfd, (struct sockaddr*) &cliaddr, &clilen); if (connfd < 0) continue; /*fork子进程来处理请求*/ if ( (childpid = fork()) == 0 ) { close(listenfd); //子进程关闭监听套接字 process_conn_server(connfd); exit(0); } close(connfd); //父进程关闭连接套接字 } } void sig_chld(int sign) { pid_t pid; int stat; /*注意信号是不排队的,使用wait的话,同时多个信号只处理一个. *使用waitpid来取得所有已终止子进程的状态; *指定WNOHANG,告诉waitpid在有未终止的子进程运行时不要阻塞; *不能在循环中调用wait,因为它会阻塞. */ while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) { printf("child %d terminated\n", pid); } return; }
/* *tcp_client.c */ #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <linux/in.h> #include <string.h> #include <signal.h> #define PORT 8888 #define BUFSIZE 1024 typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); void sig_pipe(int sign); int main (int argc, char **argv) { struct sockaddr_in servaddr; int clifd; signal(SIGPIPE, sig_pipe); if (argc != 2) { printf("usage: client server_addr\n"); exit(1); } /*设置服务器地址*/ bzero(&servaddr, sizeof(servaddr)); //清零 servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //本地地址 servaddr.sin_port = htons(PORT); if ((clifd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("socket error\n"); return -1; } /* 将用户输入的字符串类型的IP地址转为整型, * p: presentation 表达格式 ASCII串 * n: numeric 数值格式 二进制 * 第二个参数为字符串指针, 第三个参数为指向结构struct in_addr的指针 */ inet_pton(AF_INET, argv[1], &servaddr.sin_addr); if (connect(clifd, (struct sockaddr*) &servaddr, sizeof(struct sockaddr)) < 0) { printf("connect error\n"); return -1; } process_conn_client(clifd); close(clifd); } /*当服务器一端关闭时,客户端如果继续向服务器发送数据,将会 *收到一个SIGPIPE信号,我们在这里捕捉该信号. */ void sig_pipe(int sign) { printf("Catch a SIGPIPE signal\n"); }
/* *tcp_process.c */ #include <sys/types.h> void process_conn_server(int fd) { ssize_t size = 0; char buffer[1024]; for (;;) { size = read(fd, buffer, 1024); if (size == 0) return ; sprintf(buffer, "%d bytes altogether\n", size); write(fd, buffer, strlen(buffer)+1); } } void process_conn_client(int fd) { ssize_t size = 0; char buffer[1024]; for (;;) { /*从标准输入读数据*/ size = read(0, buffer, 1024); if (size > 0) { write(fd, buffer, size); //写数据给服务器 size = read(fd, buffer, 1024); //从服务器读数据 write(1, buffer, size); //输出到标准输出 } } }
编写Makefile:
CC = gcc all:client server #all规则,它依赖于client和server规则 client:tcp_process.o tcp_client.o #client规则,生成客户端可执行程序 $(CC) -o client tcp_process.o tcp_client.o server:tcp_process.o tcp_server.o #server规则,生成服务器端可执行程序 $(CC) -o server tcp_process.o tcp_server.o clean: #清理规则,删除client,server和中间文件 rm -f client server *.o
编译运行:
$ make $ ./server & $ ./client 127.0.0.1
清除中间文件及可执行文件:
$ make clean
一些说明
htonl()函数: 将一个32位数从主机字节顺序转换成网络字节顺序。htons()函数:将一个16位数从主机字节顺序转换成网络字节顺序。
inet_pton()函数:将“点分十进制” -> “二进制整数”,同时适用于IPv4和IPv6。
其中:
p: presentation 表达格式 ASCII串;
n: numeric 数值格式 二进制
在 bind 和 accept 等函数中,需要将IPv4的地址结构sockaddr_in转换成通用地址结构sockaddr。
我们使用fork产生子进程来处理请求,父进程对子进程的结束进行处理,否则会产生僵死进程。处理的信号为SIGCHLD,注意处理使用waitpid而不是wait,如果只调用一次wait,由于信号不排队,所以当有多个子进程同时死亡,发送给父进程的多个SIGCHLD只会处理1次,如果循环调用wait,在还有未结束子进程的情况下,父进程将阻塞在wait上。
当服务器已经关闭,如果客户端试图向套接字写入数据,则会产生一个SIGPIPE信号,此时将造成程序的非正常退出。我们可以捕捉该信号,并进行一些善后工作。
服务器fork子进程之后,父进程应该关闭连接套接字,否则当有其他连接到来,将可能耗尽所有的套接字描述符。另外,子进程应关闭监听套接字。
相关文章推荐
- java发送https的请求
- Android 网络请求详解
- Android 网络请求详解
- CDN技术原理
- Neutron与Nova的网络逻辑关系---Neutron节点网络逻辑关系(二)
- 配置使用连接池的httpClient
- socket 连接的建立
- HTTP 错误 401.3 - Unauthorized asp.net mvc 图片,css,js没有权限访问
- iOS安全系列之一:HTTPS (轉載)
- codeforcese 498C. Array and Operations 网络流
- The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar
- Web Service 使用时出现 HTTP Status 401: Unauthorized
- java使用http创建https连接,并且使用http实现webservice服务端
- 三种方法实现移动端HTTPS加速和省电
- Java Socket网络编程常见异常(转)
- tomcat部署成https协议
- 【常用工具类】NetUtil(检测当前网络状态)
- curl HTTP 测试常用参数总结
- Objective-c语言_计算机网络(UI)同步get,post和异步get,post
- windows 7 64位 Apache httpd 最新包的安装