计算机通信和socket 计算机程序之间的通信,是进程与进程之间的通信。之前在 Linux 学习中就遇到用管道(PIPE)来让一个进程的输出和另一个进程的输入连接起来,从而利用文件操作API来管理进程间通信。
Unix/Linux的基本哲学之一就是“一切皆文件”,一切都可以用打开(open) –> 读写(read/write) –> 关闭(close)
的模式来操作。socket可以看成一个进程向socket的一端写入或读取文本流,而另一个进程可以从另一端读取或写入。比较特别的是,这两个建立socket通信的进程可以分别属于两台不同的计算机 ,可以简单地把socket理解为一种特殊的文件,我们通过操作socket函数,实现文本流在多台计算机之间传输,同时也就实现了计算机之间的通信。
一个socket包含四个地址信息: 两台计算机的IP地址和两个进程所使用的端口(port) 。IP地址用于定位计算机,而端口用于定位进程 (一台计算机上可以有多个进程分别使用不同的端口)。
TCP和UDP 在开始 socket 编程之前,有必要简单了解一下TCP和UDP协议。
TCP(传输控制协议)提供面向连接、可靠的字节流服务 。客户端和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,从而保证数据能从一端传到另一端 。
UDP(用户数据报协议)是一个简单的面向数据报的运输层协议。UDP不提供可靠性 ,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不需在客户端和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
socket函数 1. socket() socket()
可以理解成初始化:
1 int socket (int domain, int type, int protocol) ;
参数
内容
释义
举例
1
domain
协议族(family)
常用的 domain 有 AF_INET(ipv4网络通信) 、AF_INET6(ipv6网络通信)、AF_LOCAL(或称AF_UNIX,本地通信,将绝对路径名作为地址)等
2
type
socket类型
常用的 socket type 有 SOCK_STREAM(字节流) 、SOCK_DGRAM(数据报)、SOCK_RAW(原始)、SOCK_PACKET(分组)、SOCK_SEQPACKET(有序分组)等
3
protocol
指定协议
常用的协议有,IPPROTO_TCP(TCP传输协议) 、IPPTOTO_UDP(UDP传输协议)、IPPROTO_SCTP、IPPROTO_TIPC等
type 和 protocol 并不是可以随意组合,比如使用SOCK_STREAM
(这是TCP相关类型)的时候就不能用IPPROTO_UDP
(UDP相关类型)。
2. bind() bind()
函数赋予socket以固定的地址和端口,例如AF_INET就是把一个 ipv4地址和端口号组合 赋给socket。
1 int bind (int sockfd, const struct sockaddr *addr, socklen_t addrlen) ;
参数
内容
描述
1
sockfd
socket文件描述符
2
addr
指向要绑定给sockfd的协议地址的指针,这个地址结构根据地址创建socket时的地址协议族的不同而不同
3
addrlen
地址的长度
ipv4地址结构的具体实现
1 2 3 4 5 6 7 8 9 10 11 struct sockaddr_in { sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr ; }; struct in_addr { uint32_t s_addr; };
创建socket时的地址协议族, 如果不是 AF_INET(ipv4),那么上面这个结构体的内容也是不一样的。第二个参数的指针就是指向你创建socket时对应协议族的协议地址。
3. listen()、connect() listen()
用于服务器监听,connect()
用于客户端连接。
1 int listen (int sockfd, int backlog) ;
参数
内容
描述
1
sockfd
要监听的socket描述字
2
backlog
最大连接个数
1 int connect (int sockfd, const struct sockaddr *addr, socklen_t addrlen) ;
参数
内容
描述
1
sockfd
客户端的socket描述字
2
addr
指针指向服务器的socket地址
3
addrlen
socket地址的长度
4. accept() TCP服务器端依次调用socket()
、bind()
、listen()
之后,就会开始监听指定的socket地址。
TCP客户端依次调用socket()
、connect()
之后就向TCP服务器发送了一个连接请求。
TCP服务器监听到这个请求之后,就会调用accept()
函数取接收这个请求,这样连接就建立好了。
之后就可以开始网络I/O操作,类同于普通文件的读写I/O操作。
1 int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen) ;
参数
内容
描述
1
sockfd
服务器的socket描述字
2
addr
指向struct sockaddr *的指针,用于返回客户端的协议地址
3
addrlen
协议地址的长度
accept()
的第一个参数为服务器的socket描述字,是服务器开始调用socket()
函数生成的,称为监听socket描述字;而accept()
返回的是已连接的socket描述字。
5. recvmsg() 、sendmsg() 经过上面的几个步骤,服务器与客户端已经建立好连接了。现在就可以开始调用网络I/O进行读写操作。
网络I/O操作有下面几组:
read() / write()
recv() / send()
readv() / writev()
recvmsg() / sendmsg()
recvfrom() / sendto()
它们的声明如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <unistd.h> ssize_t read (int fd, void *buf, size_t count) ; ssize_t write (int fd, const void *buf, size_t count) ; #include <sys/types.h> #include <sys/socket.h> ssize_t send (int sockfd, const void *buf, size_t len, int flags) ; ssize_t recv (int sockfd, void *buf, size_t len, int flags) ; ssize_t sendto (int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) ; ssize_t recvfrom (int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) ; ssize_t sendmsg (int sockfd, const struct msghdr *msg, int flags) ; ssize_t recvmsg (int sockfd, struct msghdr *msg, int flags) ;
具体用法可参见man文档
6. close() 在服务器与客户端建立连接之后,会进行一些读写操作,完成读写操作之后要关闭相应的socket描述字。
一个客户端与服务器通信的例子(C语言) 服务器端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #define SERVPORT 14001 #define BACKLOG 10 #define MAX_CONNECTED_NO 10 #define MAXDATASIZE 50 int main () { struct sockaddr_in server_sockaddr ,client_sockaddr ; int sin_size,recvbytes; int sockfd,client_fd; time_t currentTime; char timebuffer[MAXDATASIZE+1 ]; char buf[MAXDATASIZE]; if ((sockfd = socket(AF_INET,SOCK_STREAM,0 ))==-1 ){ perror("socket" ); exit (1 ); } printf ("socket success!,sockfd=%d\n" ,sockfd); server_sockaddr.sin_family=AF_INET; server_sockaddr.sin_port=htons(SERVPORT); server_sockaddr.sin_addr.s_addr=INADDR_ANY; bzero(&(server_sockaddr.sin_zero),8 ); if (bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof (struct sockaddr))==-1 ){ perror("bind" ); exit (1 ); } printf ("bind success!\n" ); if (listen(sockfd,BACKLOG)==-1 ){ perror("listen" ); exit (1 ); } printf ("listening....\n" ); sin_size = sizeof (struct sockaddr); if ((client_fd=accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size))==-1 ){ perror("accept" ); exit (1 ); } currentTime = time(NULL ); snprintf (timebuffer, MAXDATASIZE, "%s\n" , ctime(¤tTime)); if ((recvbytes =write(client_fd, timebuffer, strlen (timebuffer))) <0 ) { perror("write" ); exit (1 ); } while (1 ) { if ((recvbytes=recv(client_fd,buf,MAXDATASIZE,0 ))==-1 ){ perror("recv" ); exit (1 ); } buf[recvbytes] = '\0' ; printf ("received msg from clients :%s\n" ,buf); currentTime = time(NULL ); snprintf (timebuffer, MAXDATASIZE, "%s\n" , ctime(¤tTime)); write(client_fd, timebuffer, strlen (timebuffer)); } close(sockfd); }
客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #define SERVPORT 14001 #define MAXDATASIZE 300 main(int argc,char *argv[]) { int sockfd,sendbytes; char buf[MAXDATASIZE]; struct hostent * host ; struct sockaddr_in serv_addr ; if (argc < 2 ){ fprintf (stderr ,"Please enter the server's hostname!\n" ); exit (1 ); } if ((host=gethostbyname(argv[1 ]))==NULL ){ perror("gethostbyname" ); exit (1 ); } if ((sockfd=socket(AF_INET,SOCK_STREAM,0 ))==-1 ){ perror("socket" ); exit (1 ); } serv_addr.sin_family=AF_INET; serv_addr.sin_port=htons(SERVPORT); serv_addr.sin_addr=* ((struct in_addr * )host->h_addr); bzero(&(serv_addr.sin_zero),8 ); if (connect(sockfd,(struct sockaddr *)&serv_addr,\ sizeof (struct sockaddr))==-1 ){ perror("connect" ); exit (1 ); } if ((sendbytes=recv(sockfd,buf,MAXDATASIZE,0 ))==-1 ){ perror("recv" ); exit (1 ); } buf[sendbytes] = '\0' ; printf ("received msg from server:%s\n" ,buf); while (1 ) { printf ("Enter the message : " ); if (fgets(buf, sizeof (buf) - 1 , stdin ) == NULL ) { break ; } if ((sendbytes=send(sockfd,buf,strlen (buf),0 ))==-1 ){ perror("send" ); exit (1 ); } printf ("sent %d bytes \n" , sendbytes); if ((sendbytes=recv(sockfd,buf,MAXDATASIZE,0 ))==-1 ){ perror("recv" ); exit (1 ); } buf[sendbytes] = '\0' ; printf ("received time from server:%s\n" ,buf); } close(sockfd); }
参考链接