一、利用内核编程的API
sendto 和 recvfrom
sendto()_Linux C函数
sendto(经socket传送数据)
}
}
二、利用socket文件描述符
write/read
TCPServer端
[cpp] view plain copy #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <memory.h> #include <unistd.h> //#include <linux/in.h> #include <netinet/in.h> //#include <linux/inet_diag.h> #include <arpa/inet.h> #include <signal.h> /** 关于 sockaddr sockaddr_in socketaddr_un说明 http://maomaozaoyue.blog.sohu.com/197538359.html */ #define PORT 11910 //定义通信端口 #define BACKLOG 5 //定义侦听队列长度 #define buflen 1024 void process_conn_server(int s); void sig_pipe(int signo); int ss,sc; //ss为服务器socket描述符,sc为某一客户端通信socket描述符 int main(int argc,char *argv[]) { struct sockaddr_in server_addr; //存储服务器端socket地址结构 struct sockaddr_in client_addr; //存储客户端 socket地址结构 int err; //返回值 pid_t pid; //分叉进行的ID /*****************socket()***************/ ss = socket(AF_INET,SOCK_STREAM,0); //建立一个序列化的,可靠的,双向连接的的字节流 if(ss<0) { printf("server : server socket create error\n"); return -1; } //注册信号 sighandler_t ret; ret = signal(SIGTSTP,sig_pipe); if(SIG_ERR == ret) { printf("信号挂接失败\n"); return -1; } else printf("信号挂接成功\n"); /******************bind()****************/ //初始化地址结构 memset(&server_addr,0,sizeof(server_addr)); server_addr.sin_family = AF_INET; //协议族 server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本地地址 server_addr.sin_port = htons(PORT); err = bind(ss,(struct sockaddr *)&server_addr,sizeof(sockaddr)); if(err<0) { printf("server : bind error\n"); return -1; } /*****************listen()***************/ err = listen(ss,BACKLOG); //设置监听的队列大小 if(err < 0) { printf("server : listen error\n"); return -1; } /****************accept()***************/ /** 为类方便处理,我们使用两个进程分别管理两个处理: 1,服务器监听新的连接请求;2,以建立连接的C/S实现通信 这两个任务分别放在两个进程中处理,为了防止失误操作 在一个进程中关闭 侦听套接字描述符 另一进程中关闭 客户端连接套接字描述符。注只有当所有套接字全都关闭时 当前连接才能关闭,fork调用的时候父进程与子进程有相同的 套接字,总共两套,两套都关闭掉才能关闭这个套接字 */ for(;;) { socklen_t addrlen = sizeof(client_addr); //accept返回客户端套接字描述符 sc = accept(ss,(struct sockaddr *)&client_addr,&addrlen); //注,此处为了获取返回值使用 指针做参数 if(sc < 0) //出错 { continue; //结束此次循环 } else { printf("server : connected\n"); } //创建一个子线程,用于与客户端通信 pid = fork(); //fork 调用说明:子进程返回 0 ;父进程返回子进程 ID if(pid == 0) //子进程,与客户端通信 { close(ss); process_conn_server(sc); } else { close(sc); } } } /** 服务器对客户端连接处理过程;先读取从客户端发送来的数据, 然后将接收到的数据的字节的个数发送到客户端 */ //通过套接字 s 与客户端进行通信 void process_conn_server(int s) { ssize_t size = 0; char buffer[buflen]; //定义数据缓冲区 for(;;) { //等待读 for(size = 0;size == 0 ;size = read(s,buffer,buflen)); //输出从客户端接收到的数据 printf("%s",buffer); //结束处理 if(strcmp(buffer,"quit") == 0) { close(s); //成功返回0,失败返回-1 return ; } sprintf(buffer,"%d bytes altogether\n",size); write(s,buffer,strlen(buffer)+1); } } void sig_pipe(int signo) { printf("catch a signal\n"); if(signo == SIGTSTP) { printf("接收到 SIGTSTP 信号\n"); int ret1 = close(ss); int ret2 = close(sc); int ret = ret1>ret2?ret1:ret2; if(ret == 0) printf("成功 : 关闭套接字\n"); else if(ret ==-1 ) printf("失败 : 未关闭套接字\n"); exit(1); } }
TCPClient端
[cpp] view plain copy #include <stdio.h> #include <strings.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> //#include <linux/in.h> #include <stdlib.h> #include <memory.h> #include <arpa/inet.h> #include <netinet/in.h> #include <signal.h> //添加信号处理 防止向已断开的连接通信 /** 信号处理顺序说明:在Linux操作系统中某些状况发生时,系统会向相关进程发送信号, 信号处理方式是:1,系统首先调用用户在进程中注册的函数,2,然后调用系统的默认 响应方式,此处我们可以注册自己的信号处理函数,在连接断开时执行 */ #define PORT 11910 #define Buflen 1024 void process_conn_client(int s); void sig_pipe(int signo); //用户注册的信号函数,接收的是信号值 int s; //全局变量 , 存储套接字描述符 int main(int argc,char *argv[]) { struct sockaddr_in server_addr; int err; sighandler_t ret; char server_ip[50] = ""; /********************socket()*********************/ s= socket(AF_INET,SOCK_STREAM,0); if(s<0) { printf("client : create socket error\n"); return 1; } //信号处理函数 SIGINT 是当用户按一个 Ctrl-C 建时发送的信号 ret = signal(SIGTSTP,sig_pipe); if(SIG_ERR == ret) { printf("信号挂接失败\n"); return -1; } else printf("信号挂接成功\n") ; /*******************connect()*********************/ //设置服务器地址结构,准备连接到服务器 memset(&server_addr,0,sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = htonl(INADDR_ANY); /*将用户数入对额字符串类型的IP格式转化为整型数据*/ //inet_pton(AF_INET,argv[1],&server_addr.sin_addr.s_addr); printf("please input server ip address : \n"); read(0,server_ip,50); //err = inet_pton(AF_INET,server_ip,&server_addr.sin_addr.s_addr); server_addr.sin_addr.s_addr = inet_addr(server_ip); err = connect(s,(struct sockaddr *)&server_addr,sizeof(struct sockaddr)); if(err == 0) { printf("client : connect to server\n"); } else { printf("client : connect error\n"); return -1; } //与服务器端进行通信 process_conn_client(s); close(s); } void process_conn_client(int s) { ssize_t size = 0; char buffer[Buflen]; for(;;) { memset(buffer,'\0',Buflen); /*从标准输入中读取数据放到缓冲区buffer中*/ size = read(0,buffer,Buflen); // 0,被默认的分配到标准输入 1,标准输出 2,error if(size > 0) { //当向服务器发送 “quit” 命令时,服务器首先断开连接 write(s,buffer,strlen(buffer)+1); //向服务器端写 //等待读取到数据 for(size = 0 ; size == 0 ; size = read(s,buffer,Buflen) ); write(1,buffer,strlen(buffer)+1); //向标准输出写 } } } void sig_pipe(int signo) //传入套接字描述符 { printf("Catch a signal\n"); if(signo == SIGTSTP) { printf("接收到 SIGTSTP 信号\n"); int ret = close(s); if(ret == 0) printf("成功 : 关闭套接字\n"); else if(ret ==-1 ) printf("失败 : 未关闭套接字\n"); exit(1); } } 注意这里 的 sighandler_t ret;
在不同的编译环境中可能会有错误的
所以编译选项要加上
-D_GNU_SOURCE
三、基于I/O 多路复用技术的并发TCP
在实际的应用中, 要求一个服务器能同时处理大量的客户请求, 所有这些客户将访问绑 定在某一个特定套接字地址上的服务器。 因此, 服务器必须满足并发的需求。 如果不采用并 发技术, 当服务器处理一个客户请求时, 会拒绝其他客户端请求, 造成其他客户要不断的请 求并长期等待。 在 Linux( Unix) 系统中并发服务器有三种设计方式: ( 1) 多进程 进程是执行中的计算机程序, 可以认为是一个程序的一次运行。 它是一个动态的实体, 是独立的任务。 每个单独的进程运行在自己的虚拟地址空间中, 并且它只能通过安全的内核 管理机制和其它进程交互。 若是一个进程崩溃不会引起其它进程崩溃。 在 Linux(Unix)系统中, 多个进程可以同时执行相同的代码, 从而支持并发。 对于单个 CPU 系统而言, CPU 一次只能执行一个进程, 但操作系统可通过分时处理, 每个进程在每个时间段中执行, 因此对于用户而言, 这些进程在同时执行。 ( 2) 多线程 线程与进程类似, 也支持并发执行。 与进程不同的一点, 在同一进程中所有线程共享 相同的全程变量以及系统分配给进程的资源。 因此, 线程占用较少的系统资源, 并且线程之 间切换更快。 ( 3)I/O 多路复用( select 和 poll函数) 另一种支持并发的方法是 I/O多路复用。 select()函数是系统提供的, 它可以在多个描 述符中选择被激活的描述符进行操作。 例如: 一个进程中有多个客户连接, 即存在多个 TCP套接字描述符。 select()函数阻塞 直到任何一个描述符被激活, 即有数据传输。 从而避免了进程为等待一个已连接上的数据而 无法处理其他连接。 因而, 这是一个时分复用的方法, 从用户角度而言, 它实现了一个进程 或线程中的并发处理。 I/O 多路复用技术的最大优势是系统开销小, 系统不必创建进程、 线程, 也不必维护这 些进程/线程, 从而大大减少了系统的开销。 select()函数用于实现I/O 多路复用, 它允许进程指示系统内核等待多个事件中的任何一 个发生, 并仅在一个或多个事情发送或经过某指定的时间后才唤醒进程。 它的原型如下, #include<sys/time.h> int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set * errorfds, struct timeval *timeout); ndfs: select() 函数监视描述符数的最大值。 根据进程中打开的描述符数而定, 一般设为要 监视的描述符的最大数加 1。 readfds: select() 函数监视的可读描述符集合。 writefds: select()函数监视的可写描述符集合。 errorfds: select()函数监视的异常描述符集合。 timeout: select()函数超时结束时间 返回值。 如果成功返回总的位数, 这些位对应已准备好的描述符。 否则返回-1, 并在errno 中设置相应的错误码。 FD_ZERO(fd_set *fdset): 清空fdset 与所有描述符的联系 FD_SET(int fd, fd_set *fdset): 建立描述符fd 与 fdset的联系 FD_CLR(int fd, fd_set *fdset): 撤销描述符fd 与 fdset的联系 FD_ISSET(int fd,fd_set *fdset) :: 检查与fdset 联系的描述符 fd 是否可读写, 返回非 0 表示可读写。 采用 select()函数实现I/O 多路复用的基本步骤如下: ( 1) 清空描述符集合 ( 2) 建立需要监视的描述符与描述符集合的联系 ( 3) 调用 select()函数 ( 4) 检查所有需要监视的描述符, 利用 FD_ISSET 判断是否准备好 ( 5) 对已准备好的描述符进行 I/O 操作 下面是在 eHome 中使用的一个select 函数实例。 // name : Ehome_server.c // author : pyy // date : 2008-3-5 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define PORT 1234 //使用的 port号码 #define MAXSOCKFD 10 //可同时服务的最大连接数目 int main() { int sockfd,newsockfd,is_connected[MAXSOCKFD],fd; struct sockaddr_in addr; int addr_len = sizeof(struct sockaddr_in); fd_set readfds; char buffer[256]; int length; char buf2[256]; //add if((sockfd = socket(AF_INET,SOCK_STREAM,0))<0) { perror("socket"); exit(1);} //填写 sockaddr_in 结构 bzero(&addr,sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(PORT); addr.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(sockfd,(struct sockaddr *)&addr,sizeof(addr))<0) { perror("bind"); exit(1);} if(listen(sockfd,3)<0) { perror("listen"); exit(1);} //清楚连线状态的旗标 for(fd=0;fd<MAXSOCKFD;fd++) is_connected[fd] = 0; while(1) { FD_ZERO(&readfds); FD_SET(sockfd,&readfds); for(fd=0; fd<MAXSOCKFD; fd++) if(is_connected[fd]) FD_SET(fd,&readfds); if(!select(MAXSOCKFD,&readfds,NULL,NULL,NULL)) continue; //判断是否有新的连线或新信息进来 for(fd=0; fd<MAXSOCKFD; fd++) if(FD_ISSET(fd,&readfds)) { if(sockfd == fd) { //接收新连线 if((newsockfd= accept(sockfd,(struct sockaddr *)&addr,&addr_len))<0) perror("accept"); //将欢迎字符串送给 client端 is_connected[newsockfd] = 1; printf("Connect from %s\n",inet_ntoa(addr.sin_addr)); } else { //接收新信息 bzero(buffer,sizeof(buffer)); if( ( length=read(fd,buffer,sizeof(buffer)) ) <=0) { //连线已中断, 清除连线状态旗标 printf("Connection closed.\n"); is_connected[fd] = 0; close(fd); } else { printf("Receive message: %s\n",buffer); write(fd,buffer,length); bzero(buf2,sizeof(buf2)); sprintf(buf2,"%5.3f%5.3f%5.3f%5.2f%5.3f%5.2f",1.305,2.226,3.333,20.56,9.0,28.5); length= write(fd,buf2,strlen(buf2)); printf("Send message: %s length= %d\n",buf2,length); } } } } }