最近学习了下UNIX下的网络编程。为了以后查询方便,总结在这里。
首先套接字的地址定义:
IPv4地址和IPv6地址定义见<netinet/in.h>头文件定义。为了能够顺利转换不同的套接字内容,可以查看<sys/socket.h>中定义的通用套接字struct sockaddr;在使用过成中我们可以将struct sockaddr_in 和 sockaddr_in6直接强制转换成struct sockaddr.
连接过程中我们需要人工指定对应网络地址。而不同的主机实现中存在不同的数据格式(big-endian OR little-endian),我们需要通过如下转换函数来保证数据转换过程中的正确性。
函数列表如下:
#include <netinet/in.h> //from host byte order to network byte order uint16_t htons(uint16_t host16bitvalue)
//servaddr.sin_addr.s_addr = htonl(INADDR_ANY); uint32_t htonl(uint32_t host32bitvalue) //from network byte order to host byte order uint16_t ntohs(uint16_t host16bitvalue) uint16_t ntohl(uint16_t host16bitvalue)
地址转换函数使用:
#include <arpa/inet.h> int inet_pton(int family, const char* strptr, void *addrptr); //成功返回1,格式错误返回0,出错返回-1
//示例::Inet_pton(AF_INET, argv[1], &serveraddr.sin_addr); const char* inet_ntop(int family, const void*addrptr, char *strptr, size_t len); //len参数指出缓存区的大小,避免出现溢出
基本TCP套接字编程示例程序如下(这里我们read,write等系统IO操作来实现网络回射服务):
其中涉及到的网络编程函数包括:socket(),bind(),bzero(),inet_pton(), listen(),connect(),accept()
服务过程中socket()指定所要进行操作的网络服务是什么, socket(指定协议族, 操作类型, 采用协议)
协议族包括:AF_INET:Pv4; AF_INET6:IPv6; AF_LOCAL:UNIX域协议; AF_ROUTE:路由套接字; AF_KEY:密钥套接字
操作类型:SOCK_STREAM:字节流; SOCK_DGRAM:数据报; SOCK_SEQPACKET:有序分组; SOCK_RAW:原始套接字
采用协议:IPPROTO_TCP...._UDP...._SCTP
需要注意的问题:
Fork子进程后,connfd和listenfd的引用次数变成2,所以需要在子进程和父进程中同时关闭才能保证完全关闭。
多个子进程并发后,会在服务器上产生大量的僵死进程,从而使得大量的服务器资源浪费。为此我们需要捕获僵死进程的信号SIGCHLD,并做相应的处理。因为UNIX中信号不排队的设计,采用wait处理完第一个僵死进程的信号后其余的进程信号丢失。从而清理不彻底。为此,这里采用waitpid函数进行处理。
首先,服务器程序:
#include "unp.h" #include <stdio.h> #include <stdlib.h> void str_echo(int sockfd); Sigfunc* signal1(int signo, Sigfunc* func); void sig_child(int signo); int main(int argc, char** argv){ int listenfd, connfd; char buff[MAXLINE]; pid_t childpid; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; //<sys/socket.h> //int socket(int family, int type, int protocal); listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(struct sockaddr_in)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (SA*) &servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); //working with the signal1(SIGCHLD, sig_child); while(1){ clilen = sizeof(cliaddr); connfd = Accept(listenfd, (SA*)&cliaddr, &clilen); if ((childpid = Fork()) == 0){ Close(listenfd); str_echo(connfd); exit(0); } Close(connfd); } return 0; } void str_echo(int sockfd){ ssize_t n; char buf[MAXLINE]; again: while((n = read(sockfd, buf, MAXLINE) ) > 0) Write(sockfd, buf, n); if (n < 0 && errno == EINTR) goto again; else if (n < 0) err_sys("str_echo: read error"); } Sigfunc* signal1(int signo, Sigfunc* func){ struct sigaction act, oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; if ( signo == SIGALRM){ #ifdef SA_INTERRUPT //设定中断,使该信号处理过程中能够中断: act.sa_flags |= SA_INTERRUPT; #endif }else{ #ifdef SA_RESTART //信号处理,设置SA_RESTART标志,使得内核对失败的 //系统调用自动重启。 act.sa_flags |= SA_RESTART; #endif } if (sigaction(signo, &act, &oact) < 0){ return SIG_ERR; } return (oact.sa_handler); } void sig_child(int signo){ pid_t pid; int stat; /*********************************************** #inlcude <sys/wait.h> 这里使用wait函数只能处理第一个返回的僵死进程,因为其UNIX系统信号实现 中信号是不进行排队的。所以我们采用waitpid 并且设定最后的选项为WNOHANG.表示内核在没有进程时不仅行阻塞。 ***********************************************/ // --- pid = wait(&stat); while(( pid = waitpid(-1, &stat, WNOHANG)) > 0) printf("child %d terminated.\n", pid); return; }
客户端程序:
#include "unp.h" #include <stdio.h> #include <stdlib.h> int str_cli1(FILE *fp, int sockfd); int main(int argc, char** argv){ int sockfd[5]; struct sockaddr_in serveraddr; int i; if (argc < 2) err_quit("Use: clisocket <IPaddress>"); for (i = 0; i <5; ++i){ sockfd[i] = Socket(AF_INET, SOCK_STREAM,0); bzero(&serveraddr, sizeof(struct sockaddr_in)); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(SERV_PORT); Inet_pton(AF_INET, argv[1], &serveraddr.sin_addr); Connect(sockfd[i], (SA*)&serveraddr, sizeof(serveraddr)); } str_cli(stdin, sockfd[0]); exit(0); } int str_cli1(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) err_quit("str_cli: server terminated prematurely"); Fputs(recvline, stdout); } }