Linux 下socket通信终极指南(附TCP、UDP完整代码)
linux下用socket通信,有TCP、UDP两种协议,网上的很多教程把两个混在了一起,或者只讲其中一种。现在我把自己这两天研究的成果汇总下来,写了一个完整的,适合初学者参考,也方便自己以后查阅。
首先讲什么是socket,不喜欢理论的可以略过。
Berkeley套接字应用程序接口(API)包括了一个用C语言写成的应用程序开发库,主要用于实现进程间通讯,在计算机网络通讯方面被广泛使用。(来自 wikipedia socket )
下面介绍一下常用的socket API(也来自 wikipedia socket)
这个列表是一个Berkeley套接字API库提供的函数或者方法的概要:
socket()
创建一个新的确定类型的套接字,类型用一个整型数值标识,并为它分配系统资源。bind()
一般用于服务器端,将一个套接字与一个套接字地址结构相关联,比如,一个指定的本地端口和IP地址。listen()
用于服务器端,使一个绑定的TCP套接字进入监听状态。connect()
用于客户端,为一个套接字分配一个自由的本地端口号。 如果是TCP套接字的话,它会试图获得一个新的TCP连接。accept()
用于服务器端。 它接受一个从远端客户端发出的创建一个新的TCP连接的接入请求,创建一个新的套接字,与该连接相应的套接字地址相关联。send()
和recv()
,或者write()
和read()
,或者recvfrom()
和sendto()
, 用于往/从远程套接字发送和接受数据。close()
用于系统释放分配给一个套接字的资源。 如果是TCP,连接会被中断。gethostbyname()
和gethostbyaddr()
用于解析主机名和地址。select()
用于修整有如下情况的套接字列表: 准备读,准备写或者是有错误。poll()
用于检查套接字的状态。 套接字可以被测试,看是否可以写入、读取或是有错误。getsockopt()
用于查询指定的套接字一个特定的套接字选项的当前值。setsockopt()
用于为指定的套接字设定一个特定的套接字选项。
更多的细节如下给出。
[编辑]socket()
socket()
为通讯创建一个端点,为套接字返回一个文件描述符。 socket() 有三个参数:
- domain 为创建的套接字指定协议集。 例如:
PF_INET
表示IPv4网络协议PF_INET6
表示IPv6PF_UNIX
表示本地套接字(使用一个文件)
- type 如下:
SOCK_STREAM
(可靠的面向流服务或流套接字)SOCK_DGRAM
(数据报文服务或者数据报文套接字)SOCK_SEQPACKET
(可靠的连续数据包服务)SOCK_RAW
(在网络层之上的原始协议)。
- protocol 指定实际使用的传输协议。 最常见的就是
IPPROTO_TCP
、IPPROTO_SCTP
、IPPROTO_UDP
、IPPROTO_DCCP
。这些协议都在<netinet/in.h>中有详细说明。 如果该项为“0
”的话,即根据选定的domain和type选择使用缺省协议。
如果发生错误,函数返回值为-1。 否则,函数会返回一个代表新分配的描述符的整数。
- 原型:
int socket(int domain, int type, int protocol)。
[编辑]bind()
bind()
为一个套接字分配地址。当使用socket()
创建套接字后,只赋予其所使用的协议,并未分配地址。在接受其它主机的连接前,必须先调用bind()为套接字分配一个地址。bind()
有三个参数:
sockfd
, 表示使用bind函数的套接字描述符my_addr
, 指向sockaddr结构(用于表示所分配地址)的指针addrlen
, 用socklen_t字段指定了sockaddr结构的长度
如果发生错误,函数返回值为-1,否则为0。
- 原型
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
[编辑]listen()
当socket和一个地址绑定之后,listen()
函数会开始监听可能的连接请求。然而,这只能在有可靠数据流保证的时候使用,例如:数据类型(SOCK_STREAM
,SOCK_SEQPACKET
)。
listen()函数需要两个参数:
sockfd
, 一个socket的描述符.backlog
, 一个决定监听队列大小的整数,当有一个连接请求到来,就会进入此监听队列,当队列满后,新的连接请求会返回错误。当请求被接受,返回 0。反之,错误返回 -1。
原型:
int listen(int sockfd, int backlog);
[编辑]accept()
当应用程序监听来自其他主机的面对数据流的连接时,通过事件(比如Unix select()系统调用)通知它。必须用 accept()
函数初始化连接。 Accept() 为每个连接创立新的套接字并从监听队列中移除这个连接。它使用如下参数:
sockfd
,监听的套接字描述符cliaddr
, 指向sockaddr 结构体的指针,客户机地址信息。addrlen
,指向socklen_t
的指针,确定客户机地址结构体的大小 。
返回新的套接字描述符,出错返回-1。进一步的通信必须通过这个套接字。
Datagram 套接字不要求用accept()处理,因为接收方可能用监听套接字立即处理这个请求。
- 函数原型:
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
[编辑]connect()
connect()
系统调用为一个套接字设置连接,参数有文件描述符和主机地址。
某些类型的套接字是无连接的,大多数是UDP协议。对于这些套接字,连接时这样的:默认发送和接收数据的主机由给定的地址确定,可以使用 send()和 recv()。 返回-1表示出错,0表示成功。
- 函数原型:
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
TCP socket通信
服务器端流程如下:
1.创建serverSocket
2.初始化 serverAddr(服务器地址)
3.将socket和serverAddr 绑定 bind
4.开始监听 listen
5.进入while循环,不断的accept接入的客户端socket,进行读写操作write和read
6.关闭serverSocket
客户端流程:
1.创建clientSocket
2.初始化 serverAddr
3.链接到服务器 connect
4.利用write和read 进行读写操作
5.关闭clientSocket
具体实现代码如下
#server.c(TCP)
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <errno.h> #define SRVPORT 10005 #define CONNECT_NUM 5 #define MAX_NUM 80 int main() { int serverSock=-1,clientSock=-1; struct sockaddr_in serverAddr; serverSock=socket(AF_INET,SOCK_STREAM,0); if(serverSock<0) { printf("socket creation failed\n"); exit(-1); } printf("socket create successfully.\n"); memset(&serverAddr,0,sizeof(serverAddr)); serverAddr.sin_family=AF_INET; serverAddr.sin_port=htons((u_short) SRVPORT); serverAddr.sin_addr.s_addr=htons(INADDR_ANY); if(bind(serverSock,&serverAddr,sizeof(struct sockaddr_in))==-1) { printf("Bind error.\n"); exit(-1); } printf("Bind successful.\n"); if(listen(serverSock,10)==-1) { printf("Listen error!\n"); } printf("Start to listen!\n"); char revBuf[MAX_NUM]={0}; char sedBuf[MAX_NUM]={0}; while(1) { clientSock=accept(serverSock,NULL,NULL); while(1) { if(read(clientSock,revBuf,MAX_NUM)==-1) { printf("read error.\n"); } else { printf("Client:%s\n",revBuf); } if(strcmp(revBuf,"Quit")==0||strcmp(revBuf,"quit")==0) { strcpy(sedBuf,"Goodbye,my dear client!"); } else { strcpy(sedBuf,"Hello Client."); } if(write(clientSock,sedBuf,sizeof(sedBuf))==-1) { printf("Send error!\n"); } printf("Me(Server):%s\n",sedBuf); if(strcmp(revBuf,"Quit")==0||strcmp(revBuf,"quit")==0) { break; } bzero(revBuf,sizeof(revBuf)); bzero(sedBuf,sizeof(sedBuf)); } close(clientSock); } close(serverSock); return 0; }
#client.c(TCP) #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <errno.h> #include <string.h> #define SRVPORT 10005 #define CONNECT_NUM 5 #define MAX_NUM 80 int main() { int clientSock=-1; struct sockaddr_in serverAddr; clientSock=socket(AF_INET,SOCK_STREAM,0); if(clientSock<0) { printf("socket creation failed\n"); exit(-1); } printf("socket create successfully.\n"); memset(&serverAddr,0,sizeof(serverAddr)); serverAddr.sin_family=AF_INET; serverAddr.sin_port=htons((u_short) SRVPORT); serverAddr.sin_addr.s_addr=htons(INADDR_ANY); if(connect(clientSock,&serverAddr,sizeof(struct sockaddr_in))<0) { printf("Connect error.\n"); exit(-1); } printf("Connect successful.\n"); char sedBuf[MAX_NUM]={0}; char revBuf[MAX_NUM]={0}; while(gets(sedBuf)!=-1) { if(write(clientSock,sedBuf,strlen(sedBuf))==-1) { printf("send error!\n"); } printf("Me(Client):%s\n",sedBuf); bzero(sedBuf,sizeof(sedBuf)); if(read(clientSock,revBuf,MAX_NUM)==-1) { printf("rev error!\n"); } printf("Sever:%s\n",revBuf); if(strcmp(revBuf,"Goodbye,my dear client!")==0) break; bzero(revBuf,sizeof(revBuf)); } close(clientSock); return 0; }
UDP协议不能保证数据通信的可靠性,但是开销更低,编起来也更加简单
服务器流程:
1.创建serverSocket
2.设置服务器地址 serverAddr
3.将serverSocket和serverAddr绑定 bind
4.开始进行读写 sendto和recvfrom
5.关闭serverSocket
客户端流程
1.创建clientSocket
2.设置服务器地址 serverAddr
3.可选 设置clientAddr并和clientSocket(一般不用绑定)
4.进行发送操作 sendto
5.关闭clientSocket
具体代码如下:
#server.c(UDP) #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <netinet/in.h>//for sockaddr_in #include <arpa/inet.h>//for socket int main() { int fd=socket(AF_INET,SOCK_DGRAM,0); if(fd==-1) { perror("socket create error!\n"); exit(-1); } printf("socket fd=%d\n",fd); struct sockaddr_in addr; addr.sin_family=AF_INET; addr.sin_port=htons(6666); addr.sin_addr.s_addr=inet_addr("127.0.0.1"); int r; r=bind(fd,(struct sockaddr*)&addr,sizeof(addr)); if(r==-1) { printf("Bind error!\n"); close(fd); exit(-1); } printf("Bind successfully.\n"); char buf[255]; struct sockaddr_in from; socklen_t len; len=sizeof(from); while(1) { r=recvfrom(fd,buf,sizeof(buf),0,(struct sockaddr*)&from,&len); if(r>0) { buf[r]=0; printf("The message received for %s is :%s\n",inet_ntoa(from.sin_addr),buf); } else { break; } } close(fd); return 0; }
#client.c(UDP) #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <netinet/in.h>//for sockaddr_in #include <arpa/inet.h>//for socket int main() { int fd=socket(AF_INET,SOCK_DGRAM,0); if(fd==-1) { perror("socket create error!\n"); exit(-1); } printf("socket fd=%d\n",fd); struct sockaddr_in addr_to;//目标服务器地址 addr_to.sin_family=AF_INET; addr_to.sin_port=htons(6666); addr_to.sin_addr.s_addr=inet_addr("127.0.0.1"); struct sockaddr_in addr_from; addr_from.sin_family=AF_INET; addr_from.sin_port=htons(0);//获得任意空闲端口 addr_from.sin_addr.s_addr=htons(INADDR_ANY);//获得本机地址 r=bind(fd,(struct sockaddr*)&addr_from,sizeof(addr_from)); int r; if(r==-1) { printf("Bind error!\n"); close(fd); exit(-1); } printf("Bind successfully.\n"); char buf[255]; int len; while(1) { r=read(0,buf,sizeof(buf)); if(r<0) { break; } len=sendto(fd,buf,r,0,(struct sockaddr*)&addr_to,sizeof(addr_to)); if(len==-1) { printf("send falure!\n"); } else { printf("%d bytes have been sended successfully!\n",len); } } close(fd); return 0; }