在项目上做的通信方面的内容,但是呢,又没这方面的经验,从头学起,分享给需要这方面内容的朋友们。
使用socket编程有两种通信协议可以选择,一种是数据报通信,另一种是流通信。
(1)数据报通信
数据报通信协议,就是我们常说的UDP(User Data Protocol 用户数据报协议)。UDP是一种无连接的协议,这意味着我们每次发送数据报时,需要同时发送本机的socket描述符和接收端的socket描述符。因此,在每次通信时都需要发送额外的数据。
(2)流通信
流通信协议,也叫做TCP(Transfer Control Protocol,传输控制协议)。和UDP不同,TCP是一种基于连接的协议。在使用流通信之前,我们必须在通信的一对socket之间建立连接。其中一个socket作为服务器监听连接请求,另一个则作为客户端进行连接请求。一旦两个socket建立好了连接,它们就可以单向或双向进行数据传输。
进行socket编程使用UDP还是TCP呢,选择基于何种协议的socket编程取决于具体的客户端-服务器端程序的应用场景。在UDP中,每次发送数据报时,需要附带上本机的socket描述符和接收端的socket描述符。而TCP是基于连接的协议,在通信的socket对之间需要在通信之前先建立连接,因此TCP相比UDP比较耗时。在UDP中,数据报数据在大小上有64KB的限制;而TCP中也不存在这样的限制。一旦使用TCP通信的socket对建立了连接,他们之间的通信就类似IO流,所有的数据会按照发送时的顺序读取。UDP是一种不可靠的协议,发送的数据报不一定会按照其发送顺序被接收端的socket接受。而TCP是一种可靠的协议,接收端收到的包的顺序和包在发送端的顺序是一致的。
简而言之,TCP适合于如远程登录(rlogin,telnet)和文件传输(FTP)这类的网络服务。因为这些需要传输的数据大小不确定。而UDP相比TCP更加简单一些,常用来实现实时性较高或者丢包不重要的一些服务。
Socket,又称套接字,是计算机网络通信的基本技术之一。如今大部分基于网络的软件,如浏览器、即时通讯工具、P2P下载等都是基于Socket实现的。
在使用Socket编程时,需要重点了解几个API,socket()、bind()、listen()、connect()、accept()、send()和recv()。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept()阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把处理结果发送给客户端,客户端读取数据,最后关闭连接,一次完整的交互结束。
(1)socket()
int socket(int protofamily, int type, int protocol);//返回sockfd
返回值:如果函数调用成功,会返回一个标识这个套接字的文件描述符(大于零的数);失败返回-1。
函数的三个参数分别为:
protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)、AF_INET6(IPV6)、 AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定 了要用一个绝对路径名作为地址。
type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、 SOCK_PACKET、SOCK_SEQPACKET等。
protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、 IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC 传输协议。
(2)bind()
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值:正常返回0,错误返回-1
参数:
sockfd:即socket描述字,通过socket()函数创建,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。
addrlen:对应的是地址的长度。
(3)listen()
int listen(int sockfd, int backlog);
返回值:正常返回0,错误返回-1
参数:
sockfd:要监听的socket描述字,
backlog:为相应socket可以排队的最大连接个数。
socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户端的连接请求。
(4)connect()
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值:成功则返回0, 失败返回-1, 错误原因存于errno 中.
参数:
sockfd:客户端的socket描述字
addr:服务器的socket地址
addrlen:socket地址的长度
(5)accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //返回连接connect_fd
返回值:成功时返回一个新的SOCKFD值(非负值),失败时返回-1
参数:
sockfd是监听套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个端口号,而此时这个端口号与这个套接字关联。
addr是一个结果参数,它用来接受一个返回值,返回值指定客户端的地址,可以置为NULL。
addrlen它也是结果参数,用来接受上述addr的结构的大小,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。
(6)send()
不论是客户端还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。
int send( SOCKET s, const char FAR *buf, int len,int flags);
返回值:成功返回所发送数据的总数;对端连接关闭返回0;失败返回-1
参数:
第一个参数指定发送端套接字描述符;
第二个参数指明一个存放应用程序要发送数据的缓冲区;
第三个参数指明实际要发送的数据的字节数;
第四个参数一般置0。
(7)recv()
不论是客户端还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。
int recv( SOCKET s, char FAR *buf, int len,int flags);
返回值:成功返回其实际copy的字节数;对端连接关闭返回0;失败返回-1
参数:
第一个参数指定接收端套接字描述符;
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
第三个参数指明buf的长度;
第四个参数一般置0。
(8)close()
int close(int fd);
头文件:#include
返回值:成功返回0,出错返回-1
参数:要关闭的套接字描述符
注意,实际开发中,不要去send 0字节的数据,这是一种很糟糕的做法。这会使函数的返回值0产生歧义。recv和send不一定是一一对应的(一般情况下是一一对应),也就是说并不是send一次,就一定recv一次就接收完,有可能send一次,recv多次才接收完;也可能send多次,一次recv就接收完了。
附按照上面的顺序编写的小demo:
server.cpp
/**
TCP server(阻塞式)
**/
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char** argv){
int server_socket;//服务端套接字
int client_socket;//客户端套接字
struct sockaddr_in servaddr;//服务器网络地址
struct sockaddr_in remote_addr;///客户端地址
char buff[BUFSIZ];//数据传送缓冲区
int n;
socklen_t addr_size;//结构体长度,accept函数中该参数类型为socklen_t *
memset(&servaddr, 0, sizeof(servaddr));//结构体初始化清零
servaddr.sin_family = AF_INET;//IPv4
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(8888);
addr_size = sizeof(struct sockaddr_in);
//创建服务端套接字
//1. create socket
if( (server_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0 ){
printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
return -1;
}
//绑定套接字到ip和端口
//2.bind socket & port to server
if( bind(server_socket, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
return -1;
}
//监听连接请求,监听队列长度为10
//3.listen
if( listen(server_socket, 10) < 0){
printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
return -1;
}
printf("======waiting for client's request======\n");
while(true){
//等待客户端连接请求到达
//4.accept
if( (client_socket = accept(server_socket, (struct sockaddr *)&remote_addr, &addr_size)) < 0){
printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
continue;
}
printf("conncet with client: %s\n",inet_ntoa(remote_addr.sin_addr));//
if(send(client_socket,"welcome\n",7,0) < 0){
printf("send message error: %s(errno: %d)",strerror(errno),errno);
continue;
}
//5.read message from socket
while((n = recv(client_socket, buff, BUFSIZ, 0)) > 0){
buff[n] = '\0';
printf("receive message from client: %s\n", buff);
if(send(client_socket,buff,strlen(buff),0) < 0){
printf("send message error: %s(errno: %d)",strerror(errno),errno);
close(client_socket);
close(server_socket);
return -1;
}
}
}
//关闭套接字
//close(client_socket);
//close(server_socket);
return 0;
}
client.cpp
/**
TCP client(阻塞式)
**/
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char** argv){
int client_socket; //客户端套接字
int n;
char buff[BUFSIZ];//数据传输缓冲区
struct sockaddr_in remote_addr;//服务器端网络地址
memset(&remote_addr, 0, sizeof(remote_addr));
remote_addr.sin_family = AF_INET;
remote_addr.sin_port = htons(8888);
remote_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
//创建客户端套接字
if( (client_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
return -1;
}
//连接服务器
if( connect(client_socket, (struct sockaddr*)&remote_addr, sizeof(remote_addr)) < 0){
printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
return -1;
}
printf("connected to server success\n");
//接收服务器欢迎消息
if( (n = recv(client_socket,buff,BUFSIZ,0)) > 0){
buff[n] = '\0';
printf("receive message from server: %s\n", buff);
}
/*循环发送接收消息并打印*/
while(true){
printf("send message to server:");
scanf("%s",buff);
//输入quit,则client与server断开连接
if( !strcmp(buff,"quit")){
printf("客户端断开连接...\n");
close(client_socket);
break;
}
//send to server
if((n = send(client_socket,buff,strlen(buff),0)) < 0){
printf("send message error: %s(errno:%d)\n",strerror(errno),errno);
}
//receive from server
if((n = recv(client_socket,buff,BUFSIZ,0)) > 0){
buff[n] = '\0';
printf("recaeived:%s\n",buff);
}
}
close(client_socket);
return 0;
}
运行截图:
刚开始学习socket编程,不足之处请大家多多指点。