近期在作的项目中,涉及到了进程间数据传输,系统的本来实现是经过管道,可是原有的实现中两个进程是在同一台机器,并且两个进程的关系为父子关系,而咱们要作的是将其中一个进程移植到服务器上,所以两个进程要分开,因此管道必然是不可行的方案,而对于其它的进程通讯方式,FIFO,消息队列,信号量和共享内存,显然也是不可行的。所以采起了经过socket的通讯方式,即网络套接字,用来作数据的传输。接下来,将对本身对socket的学习一个整理,socket是什么?socket的建立,绑定,发送,接收消息过程进行分析,同时附带一个简单的代码实例。程序员
套接字是通讯端点的抽象,其英文socket,即为插座,孔的意思。若是两个机子要通讯,中间要经过一条线,这条线的两端要链接通讯的双方,这条线在每一台机子上的接入点则为socket,即为插孔,因此在通讯前,咱们在通讯的两端必需要创建好这个插孔,同时为了保证通讯的正确,端和端之间的插孔必需要一一对应,这样两端即可以正确的进行通讯了,而这个插孔对应到咱们实际的操做系统中,就是socket文件,咱们再建立它以后,就会获得一个操做系统返回的对于该文件的描述符,而后应用程序能够经过使用套接字描述符访问套接字,向其写入输入,读出数据。
站在更贴近系统的层级去看,两个机器间的通讯方式,无非是要经过运输层的TCP/UDP,网络层IP,所以socket本质是编程接口(API),对TCP/UDP/IP的封装,TCP/UDP/IP也要提供可供程序员作网络开发所用的接口,这就是Socket编程接口。
Socket的建立编程
#include <sys/socket.h> int socket (int domain, int type, int protocol);
int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
这样,咱们便建立了一个socket,对于socket接收的参数都有什么意义呢?从上面,咱们能够知道socket是对于底层网络通讯的一个封装,而对于底层的网络通讯也是具有多种类型的。而这些参数则是经过组合来表示各种通讯的特征,从而创建正确的套接字。服务器
type:套接字的类型,进一步肯定通讯特征。
protocol:表示为给定域和套接字类型选择默认协议,当对同一域和套接字类型支持多个协议时,能够经过该字段来选择一个特定协议,一般默认为0.上面设置的socket类型,在执行的时候也会有默认的协议类型提供,好比SOCK_STREAM就TCP协议。
从上面的socket类型中,咱们看到有SOCK_RAW该种类型,SOCK_RAW套接字提供一个数据报接口。经过这个咱们能够直接访问下面的网络层,绕过TCP/UDP,所以咱们能够进行制定本身的传输层协议。
至此,咱们的socket已经建立出来了,当咱们再也不使用的时候,咱们能够调用close函数来将其关闭,释放该文件描述符,这样即可以获得从新的使用。
套接字通讯是双向的,可是,咱们能够采用shutdown函数来禁止一个套接字的I/O.网络
#include<sys/socket.h> int shutdown(int sockfd, int how);
how能够用来指定读端口或者是写端口,这样咱们即可以关闭掉读端或者写端。架构
我么已经建立好了Socket,接下来要作的就是经过socket进行通讯了,在两个进程间进行通讯,首先,咱们要找到这些进程,找到进程,也就是可以有这些进程的惟一标示,有了这些标示,咱们才能够肯定通讯的双方,而后进行数据的传输,对于一个通讯进程的标示,所采起的方式是经过一个网络地址,也就是IP地址,战找到咱们要通讯的主机,而后经过端口号,找到相应的服务。网络地址+端口号惟一标示了一个咱们要通讯的目标进程。框架
字节序是处理器架构的特性,用来指示像整数这种数据类型的内部如何排序,大端和小端,所以若是通讯双方的处理器架构不一样,则会致使字节序的不一致的问题出现。最底层的网络协议指定了字节序,大端字节序,可是应用程序在处理数据时,则会遇到字节序不一致的问题。对此,系统提供了进行处理器字节序和网络字节序之间实施转换的函数。dom
#include <arpa/inet.h> uint32_t htonl(uint32_t hostint32)//主机字节转化为网络字节序 uint16_t htons(uint16_t hostint16) uint32_t ntohl(uint32_t netint32)//网络字节序转化为主机字节序 unint16_t ntohs(uint16_t netint16)
上面,咱们已经谈到如何表示一个要通讯的进程,须要一个网络地址和端口,而在系统中如何具体的标示这一特征呢?根据以前socket的建立,咱们知道不一样socket对应了不一样的通讯特征,而对于不一样的通讯特征,其地址表示上也有一些差异。
这里咱们只看一下IPV4因特网域地址的表示结构。
struct sockaddr_in { sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr;}
sin_family: 通讯的的域,这里为AF_INET
sin_port:通讯的端口
sin_addr:网络地址socket
咱们套接字已经建立好了,地址结构也已经了解了,接下来就是要将套接字和地址进行关联,关联的方法则是经过bind
函数。函数
#include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
建立地址学习
struct sockaddr_in server_sockaddr;server_sockaddr.sin_family = AF_INET;server_sockaddr.sin_port = htons(PORT);server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); socklen_t server_len = sizeof(server_sockaddr); bind(server_sockfd, (struct sockaddr*)&server_sockaddr, server_len)
经过bind函数,咱们实现了socket和地址的绑定。
创建链接
socket创建好了,地址也绑定好了,这个时候,咱们就能够进行链接了,要有一方进行链接的创建,经过调用connect
函数。
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
sockfd:这个就是本地端socket描述符,若是咱们没有赋值,系统会默认提供一个值。只有当服务器开启,并正常运行,咱们的链接才可以正常创建。
如何让socket接收链接请求呢?在另外一端,咱们调用listen
方法来接收链接请求。
#include <sys/socket.h>int listen(int sockfd, int backlog);
#include <sys/socket.h> int accept(int sockfd, struct sockaddr *restric addr, socklen_t *restrict len);
调用accept函数的返回值是套接字文件描述符,该描述符链接到调用connect的客户端。
一旦服务器调用了listen,所用的套接字就能接收链接请求,使用accept函数得到链接请求并创建链接。
使用accept函数得到链接请求并创建链接。
int accept(int sockfd, struct sockaddr *restrict addr, socklent_t *restrict len);
当调用accept函数会产生一个新的套接字,这个新的套接字和原始套接字有相同的套接类型。这个时候,咱们能够传入一个指向socket的指针和其大小,设置以后,调用了accept就会将客户端的地址进行缓冲。
数据传输
链接已经创建好了,因为socket自己都是文件描述符,所以接下来就能够调用所read和write来经过套接字通讯。
对于面向链接的数据传输,咱们须要的两个函数是send和recv。
#include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags)
对于不一样的socket类型,系统提供了不一样的发送方法。
#include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags)
具体参数和send相似。
socket选项设置
对于Socket,系统提供了更具体细致化的一些配置选项,经过这些配置选项,咱们能够进行进一步具体的配置。
#include <sys/socket.h> int setsockopt(int sockfd, int level, int option, const void *val, socklen_t len);
sockfd:咱们要进行配置的socket
level:根据咱们选用的协议,配置相应的协议编号
option:选项即为上表
最后参数是用来存放返回值
实现demo实例
server
#include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <sys/shm.h> #define PORT 22468 #define KEY 123 #define SIZE 1024 int main() { char buf[100]; memset(buf,0,100); int server_sockfd,client_sockfd; socklen_t server_len,client_len; struct sockaddr_in server_sockaddr,client_sockaddr; /*create a socket.type is AF_INET,sock_stream*/ server_sockfd = socket(AF_INET,SOCK_STREAM,0); server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(PORT); server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); server_len = sizeof(server_sockaddr); int on; setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR,&on,sizeof(on)); /*bind a socket or rename a sockt*/ if(bind(server_sockfd, (struct sockaddr*)&server_sockaddr, server_len)==-1){ printf("bind error"); exit(1); } if(listen(server_sockfd, 5)==-1){ printf("listen error"); exit(1); } client_len = sizeof(client_sockaddr); pid_t ppid,pid; while(1) { if((client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_sockaddr, &client_len)) == -1){ printf("connect error"); exit(1); } else { printf("create connection successfully\n"); int error = send(client_sockfd, "You have conected the server", strlen("You have conected the server"), 0); printf("%d\n", error); } } return 0; }
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> #include <unistd.h> #include <arpa/inet.h> #define SERVER_PORT 22468 #define MAXDATASIZE 100 #define SERVER_IP "Your IP" int main() { int sockfd, numbytes; char buf[MAXDATASIZE]; struct sockaddr_in server_addr; printf("\n======================client initialization======================\n"); if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); bzero(&(server_addr.sin_zero),sizeof(server_addr.sin_zero)); if (connect(sockfd, (struct sockaddr *)&server_addr,sizeof(struct sockaddr_in)) == -1){ perror("connect error"); exit(1); } while(1) { bzero(buf,MAXDATASIZE); printf("\nBegin receive...\n"); if ((numbytes = recv(sockfd, buf, MAXDATASIZE, 0)) == -1){ perror("recv"); exit(1); } else if (numbytes > 0) { int len, bytes_sent; buf[numbytes] = '\0'; printf("Received: %s\n",buf); printf("Send:"); char msg[100]; scanf("%s",msg); len = strlen(msg); //sent to the server if(send(sockfd, msg,len,0) == -1){ perror("send error"); } } else { printf("soket end!\n"); break; } } close(sockfd); return 0; }
最近也在看的一个RPC框架,thrift,定义好咱们的接口文件,而后能够帮助咱们生成两端的桩文件,并且实现原理上,也不过是经过底层的socket通讯作了包装,执行相应的调用。socket通讯在大三的OS课上写过,本文主要目的记录本次学习,对于socket知识进行了一个回顾。