Linux Socket 摘要(二)(基于TCP的C/S基本实现,相关基础知识,非阻塞select)
来源:互联网 发布:淘宝卖家如何加入村淘 编辑:程序博客网 时间:2024/05/21 04:20
#PS:要转载请注明出处,本人版权所有
#PS:这个只是 《 我自己 》理解,如果和你的
#原则相冲突,请谅解,勿喷
测试环境:
Linux 4.10.0-33-generic #37~16.04.1-Ubuntu SMP Fri Aug 11 14:07:24 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
1 关于linux socket通信,要详细了解清楚,不知道要说多少天。所以网上大部分教程也是只介绍了基本的api调用流程。一些其他的问题还没有提及,当然本文作者由于水平有限,估计也只能介绍个流程,并且解决一些简单的未涉及的问题。
2 TCP的基本要点,三次握手,四次分手,分别代表了开始和结束。下图是我在百度图片上找的一个图,完全找不到原图出自哪里,很伤感。
说明:此图完全清晰可见的描述了一个tcp通信到底做了一些什么。我也不详细说明,改天可以给大家抓包分析分析。
3 通过上图我们可以看到,客户端connect后,就可以write和read了,而服务端accept后可以做同样的事情,最后只需要close就能够解决。下面我们简要的来分析一下这个流程。
上图是我写的一个服务端程序跑起来后,通过netstat可以看到此进程进入了listen状态。
上面三个图演示了一个tcp通信的完整过程。第三图1-3是connect,4是write:Hello Server,5是对4的响应,代表收到,6是write:Hello Client,7同理5,8-11对应close,和上文所要展示的流程基本相同。
上图是查看端口,可见tcp的链接状况(图中pid和上文图中pid不对应的原因是非同一个测试)。
4 好了,上文BB了那么多,只是要科普一下而已,现在进入正题,首先来看几个定义及结构体。
结构体1
typedef unsigned short __kernel_sa_family_t; typedef __kernel_sa_family_t sa_family_t; struct sockaddr {//通用结构体,很多socket相关api都要使用它 sa_family_t sa_family; char sa_data[14]; }
结构体2
typedef uint32_t in_addr_t; struct in_addr//ip地址存放结构体 { in_addr_t s_addr; }; #define __SOCKADDR_COMMON(sa_prefix) \ sa_family_t sa_prefix##family struct sockaddr_in//ip4 地址结构 { __SOCKADDR_COMMON (sin_); in_port_t sin_port; //Port number. struct in_addr sin_addr; // Internet address. // Pad to size of `struct sockaddr' unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)]; };
结构体3
#define _K_SS_MAXSIZE 128 //Implementation specific max size #define _K_SS_ALIGNSIZE (__alignof__ (struct sockaddr *)) //Implementation specific desired alignment typedef unsigned short __kernel_sa_family_t; struct __kernel_sockaddr_storage {//此结构体是新内核提出的,可以存储所有协议地址类型 __kernel_sa_family_t ss_family; //address family // Following field(s) are implementation specific char __data[_K_SS_MAXSIZE - sizeof(unsigned short)]; // space to achieve desired size, // _SS_MAXSIZE value minus size of ss_family } __attribute__ ((aligned(_K_SS_ALIGNSIZE))); /* force desired alignment
结构体1,2是ip4网络编程常用的一些结构体,结构体3是新内核对于多种协议地址结构提出的一个新的通用的存储结构体。
5 关于这些结构体的知识我们就到这里了。现在来讲一讲linux上的socket编程。下面就是cs通信中,各自要使用的api及调用顺序,这也是最基本的socket通信,也是网上流传最广的通信例子。
client:int socket(int domain, int type, int protocol);int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);read/writecloseserver:int socket(int domain, int type, int protocol);int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);int listen(int sockfd, int backlog);int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//阻塞ioread/writeclose
6 非阻塞通信,关键select(注意,这里没有使用更高级的epoll,因为我对这个api也是一个菜鸡)
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); void FD_CLR(int fd, fd_set *set); int FD_ISSET(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_ZERO(fd_set *set);
对于这个api,简单来说,就是监控 所有传入的 文件描述符集合,当相关文件描述集合可读, 可写 ,异常时,select正常返回,否则可能是等待超时,可能是出错。
对于本文,就是监控客户端socket fd,监控服务端socket fd和accept接受的fd。
7 其他的都不说了,口水都干了,直接上例子代码,然后喝口水,上个厕所,洗个手,坐等下班
client.c
#include <stdio.h>#include <unistd.h>#include <sys/types.h> /* See NOTES */#include <sys/socket.h>#include <errno.h>#include <string.h>#include <arpa/inet.h>#include <sys/select.h>/* According to earlier standards */#include <sys/time.h>#include <sys/types.h>#include <unistd.h>#define TARGETPORT 6666#define TARGETIP "127.0.0.1"int main(int argc, char *argv[]){ /*int socket(int domain, int type, int protocol);domain: AF_UNIX, AF_LOCAL Local communication unix(7) AF_INET IPv4 Internet protocols ip(7) AF_INET6 IPv6 Internet protocols ipv6(7) AF_IPX IPX - Novell protocols AF_NETLINK Kernel user interface device netlink(7) AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7) AF_AX25 Amateur radio AX.25 protocol AF_ATMPVC Access to raw ATM PVCs AF_APPLETALK AppleTalk ddp(7) AF_PACKET Low level packet interface packet(7) AF_ALG Interface to kernel crypto APItype: SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mecha‐ nism may be supported. SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length). SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection- based data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire packet with each input system call. SOCK_RAW Provides raw network protocol access. SOCK_RDM Provides a reliable datagram layer that does not guar‐ antee ordering. SOCK_PACKET Obsolete and should not be used in new programs; see packet(7).protocol: The protocol specifies a particular protocol to be used with the socket. Normally only a single protocol exists to support a particular socket type within a given protocol family, in which case protocol can be specified as 0. However, it is possible that many protocols may exist, in which case a particular protocol must be specified in this manner. The protocol number to use is specific to the “communication domain” in which communication is to take place; see protocols(5). See getprotoent(3) on how to map protocol name strings to protocol numbers.*/ int fd; if (0 > (fd = socket(AF_INET, SOCK_STREAM, 0))) { perror("socket create error:"); return -1; } /* int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); typedef unsigned short __kernel_sa_family_t; typedef __kernel_sa_family_t sa_family_t; struct sockaddr { sa_family_t sa_family; char sa_data[14]; } typedef uint32_t in_addr_t; struct in_addr { in_addr_t s_addr; }; #define __SOCKADDR_COMMON(sa_prefix) \ sa_family_t sa_prefix##family struct sockaddr_in { __SOCKADDR_COMMON (sin_); in_port_t sin_port; /* Port number. struct in_addr sin_addr; /* Internet address. // Pad to size of `struct sockaddr'. unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)]; }; #define _K_SS_MAXSIZE 128 //Implementation specific max size #define _K_SS_ALIGNSIZE (__alignof__ (struct sockaddr *)) //Implementation specific desired alignment typedef unsigned short __kernel_sa_family_t; struct __kernel_sockaddr_storage { __kernel_sa_family_t ss_family; /* address family /* Following field(s) are implementation specific char __data[_K_SS_MAXSIZE - sizeof(unsigned short)]; /* space to achieve desired size, */ /* _SS_MAXSIZE value minus size of ss_family } __attribute__ ((aligned(_K_SS_ALIGNSIZE))); /* force desired alignment */ struct sockaddr_in addr; memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_port = htons(TARGETPORT); //addr.sin_addr.s_addr = htonl //int inet_pton(int af, const char *src, void *dst); inet_pton(AF_INET, TARGETIP, (void *)&(addr.sin_addr.s_addr)); //int connect(int sockfd, const struct sockaddr *addr,\ socklen_t addrlen); if (0 > connect(fd, (const struct sockaddr *)&addr, sizeof(struct sockaddr))) { perror("socket connect error:"); return -1; } int ret; struct timeval timeout; char SendMsg[] = {"Hello Server"}; char RecBuf[100] = {0}; fd_set rec_set; FD_ZERO(&rec_set); FD_SET(fd, &rec_set); while (1) { //ssize_t write(int fd, const void *buf, size_t count); if ( 0 > write(fd, SendMsg, sizeof(SendMsg)) ){ printf("write failed\n"); } timeout.tv_sec = 5; timeout.tv_usec = 0; //int select(int nfds, fd_set *readfds, fd_set *writefds,\ fd_set *exceptfds, struct timeval *timeout); ret = select(fd + 1, &rec_set, NULL, NULL, &timeout); //select返回表示检测到可读事件 switch(ret){ case 0:{ printf("select timeout!\n"); break; } case -1:{ perror("select error:"); return -1; break; } default:{ //ssize_t read(int fd, void *buf, size_t count); read(fd, RecBuf, sizeof(SendMsg)); printf("RecBuf:%s\n",RecBuf); sleep(1); } } } close(fd); return 0;}
server.c
#include <stdio.h>#include <unistd.h>#include <sys/types.h> /* See NOTES */#include <sys/socket.h>#include <errno.h>#include <string.h>#include <arpa/inet.h>#include <sys/select.h>/* According to earlier standards */#include <sys/time.h>#include <sys/types.h>#include <sys/select.h>#define TARGETPORT 6666#define TARGETIP "127.0.0.1"typedef struct _MyFdSet{ int FdNum; int AllFdSet[FD_SETSIZE];} MyFdSet;void MyFdSet_INSERT(MyFdSet *set, int fd){ set->AllFdSet[set->FdNum] = fd; set->FdNum++; }int MyFdSet_GETMAX(MyFdSet *set){ int i = 1; int max_fd = set->AllFdSet[0]; for ( ; i < set->FdNum; i ++) { if ( max_fd < set->AllFdSet[i]){ max_fd = set->AllFdSet[i]; } } return max_fd;}void MyFdSet_REMOVE(MyFdSet * set,int fd) { MyFdSet tmp; tmp.FdNum = 0; int i = 0; for ( ; i < set->FdNum; i++ ){ if ( fd != set->AllFdSet[i] ){ tmp.AllFdSet[tmp.FdNum] = set->AllFdSet[i]; tmp.FdNum++; } } set->FdNum = tmp.FdNum; for ( i = 0; i < set->FdNum; i++){ set->AllFdSet[i] = tmp.AllFdSet[i]; }}int main(int argc, char *argv[]) { int fd; if (0 > (fd = socket(AF_INET, SOCK_STREAM, 0))) { perror("socket error:"); return -1; } struct sockaddr_in addr; struct sockaddr_in client_addr; memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_port = htons(TARGETPORT); addr.sin_addr.s_addr = htonl(INADDR_ANY); // int bind(int sockfd, const struct sockaddr *addr,\ socklen_t addrlen); if (0 > bind(fd, (const struct sockaddr *)&addr, sizeof(struct sockaddr))) { perror("bind error:"); return -1; } //int listen(int sockfd, int backlog); if (0 > listen(fd, 5)) { perror("bind error:"); return -1; } fd_set ser_set; FD_ZERO(&ser_set); FD_SET(fd, &ser_set); struct timeval timeout; int ret; MyFdSet myfdset = {0,{0}}; MyFdSet_INSERT(&myfdset, fd); char RecBuf[100]; char SendMsg[] = {"Hello Client"}; while (1) { int n = 0; FD_ZERO(&ser_set); for ( ; n < myfdset.FdNum; n++){ FD_SET(myfdset.AllFdSet[n], &ser_set); printf("exist fd %d\n",myfdset.AllFdSet[n]); } timeout.tv_sec = 5; timeout.tv_usec = 0; //int select(int nfds, fd_set *readfds, fd_set *writefds,\ fd_set *exceptfds, struct timeval *timeout); ret = select( MyFdSet_GETMAX(&myfdset) + 1, &ser_set, NULL, NULL, &timeout); switch (ret) { case 0: { printf("select timeout!\n"); break; } case -1: { perror("select error:"); return -1; break; } default: { int i = 0; for ( ; i < myfdset.FdNum; i ++ ){ if (FD_ISSET(myfdset.AllFdSet[i], &ser_set)){ if ( fd == myfdset.AllFdSet[i] ){//client connect int c_fd; int client_addr_len = sizeof(struct sockaddr); if ( 0 > (c_fd = accept(fd, (struct sockaddr*)&client_addr, &client_addr_len)) ){ perror("accept error:"); break; } printf("client ip:%s,port:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); MyFdSet_INSERT(&myfdset, c_fd); FD_SET(c_fd, &ser_set); break; } else{//client sent datas in buf ret = read(myfdset.AllFdSet[i], RecBuf, sizeof(SendMsg)); if ( 0 > ret ){ MyFdSet_REMOVE(&myfdset, myfdset.AllFdSet[i]); close(myfdset.AllFdSet[i]); perror("read error:"); break; } else if( ret == 0 ){//client disconnected MyFdSet_REMOVE(&myfdset, myfdset.AllFdSet[i]); close(myfdset.AllFdSet[i]); printf("client disconnected\n"); break; } else{ printf("RecMsg:%s\n", RecBuf); write(myfdset.AllFdSet[i], SendMsg, sizeof(SendMsg)); } } } } sleep(1); } } } int i; for ( i = 0; i < myfdset.FdNum; i++ ){//close fd close(myfdset.AllFdSet[i]); } return 0; }
直接gcc client.c -o client gcc server.c -o server 就可以使用了
#PS:请尊重原创,不喜勿喷
#PS:要转载请注明出处,本人版权所有.
有问题请留言,看到后我会第一时间回复
- Linux Socket 摘要(二)(基于TCP的C/S基本实现,相关基础知识,非阻塞select)
- linux c实现超时、非阻塞socket的函数select
- [Linux]基于select的Socket编程实现客户端群聊[非阻塞]
- Windows网络编程之(二)Socket通信非阻塞模式Select(TCP和UDP)
- 非阻塞式socket编程(select() ) - [linux开发]
- 非阻塞式socket编程(select() ) - [linux开发]
- linux socket 非阻塞select
- TCP Socket编程(非阻塞模式)(C++)
- TCP Socket编程(非阻塞模式)(C++)
- soket编程相关(二)如何判断连接状态断开,Linux:C/Socket多路复用select(),阻塞
- 非阻塞式socket编程(select() )
- 非阻塞式socket编程(select() )
- 非阻塞式socket编程(select() )
- 非阻塞式socket编程(select() )
- select 实现 socket 的非阻塞(转,神一般的存在)
- 非阻塞socket通讯(select函数的使用)
- 手机端基于select/poll的非阻塞Socket
- [linux C]使用select进行非阻塞socket通信
- 在安卓中使用正则表达式3
- Android xUtils3完全解析
- jdbc高级
- elasticsearch5.4 ik分词器插件安装
- 7.软件包机制
- Linux Socket 摘要(二)(基于TCP的C/S基本实现,相关基础知识,非阻塞select)
- HTML基础
- 运行npm run dev 时报错npm run install --save !!vue-style-loader!css-loader?
- ShaderGUI: Custom Material Inspectors in Unity 5+
- js判断变量是否未定义的代码
- 。net 后台将List数据转换为json后 前台js 调用
- 翻转字符串算法
- Spring IoC 学习(3)
- 第四章——文本输入和委托模式