Linux下的Socket编程大体上包括Tcp Socket、Udp Socket即Unix Domain Socket这三种,其中TCP和UDP方式的Socket编程用于编写应用层的socket程序,是我们用得比较多的,Unix Domain Socket主要用于unix的本地通信。
基于TCP协议的客户端/服务器程序的一般流程一般如下:
它基本上可以分为三个部分:
一、建立连接:
二、传输数据:
建立连接后,TCP协议提供全双工的通信管道,服务器端和客户端根据协议可以通过read和write的反复调用实现数据的传输
三、关闭连接:
当数据传输已经完成后,服务器和客户端可以调用Close关闭连接,一端关闭连接后,另一端read函数则会返回0,可以根据这个特征来感应另一端的退出。
下面就以一个简单的EchoServer演示一下如何创建服务器端和客户端代码,其中和socket相关api都会高亮显示。
服务器端步骤:
1. socket(int domain,int type,int protocol):建立套接字;
2 .bind(int sockid,struct sockaddr *addrp,socklen_t addrlen):把本机地址和端口跟上一步建立的socket绑定在一起;
3. listen(int sockid,int qsize):监听某套接字;
4. fd=accept(int sockid,struct sockaddr *callerid,socklen_t *addrlenp):等待某套接字接收信息;
5. read(int fd,void *buf,size_t nbytes):从套接字接收数据;
6. close(fd) 和close(sockid)
#include <iostream> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> using namespace std; #define MAXLINE 80 #define SERV_PORT 8000 int main() { //设置一个socket地址结构server_addr,代表服务器internet地址, 端口 struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; int listenfd, connfd; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; //创建用于internet的流协议(TCP)socket,用server_socket代表服务器socket listenfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr));//把一段内存区的内容全部设置为0 servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); listen(listenfd, 20); cout<<"Accepting connections ..."<<endl; cliaddr_len = sizeof(cliaddr); while (1) { connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); int n = read(connfd, buf, MAXLINE); if(n>0){ cout<<"received from "<<inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)) <<" at PORT "<<cliaddr.sin_port<<":"<<buf<<endl; } for (int i = 0; i < n; i++)//将从client接收到的字母转化为大写,回送给client buf[i] = toupper(buf[i]); n = write(connfd, buf, sizeof(buf)); } close(connfd); close(listenfd); }
客户端步骤:
1. socket():建立套接字;
2.connect(int sockid,struct sockaddr *serv_addrp,socklen_t addrlen):连接到服务器;
3. write(int sockfd,const void *buf,size_t nbytes):发送数据到服务器.
4. close(sockid);
#include <iostream> #include <string.h> #include <stdio.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> using namespace std; #define MAXLINE 80 #define SERV_PORT 8000 int main(int argc, char *argv[]) { char buf[MAXLINE]; int sockfd = socket(AF_INET, SOCK_STREAM, 0); sockaddr_in servaddr = {0}; servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); if (0 != connect(sockfd, (sockaddr *)&servaddr, sizeof(servaddr))) { cout<<"connected failed"<<endl; return 1; } char message[20]; cin>>message; int count = write(sockfd, message, sizeof(message)); if(count > 0){ cout<<"send to server:"<<message<<endl; }else{ cout<<"fail send to server"<<endl; return 1; } count = read(sockfd, buf, sizeof(message)); if(count > 0){ cout<<"response from server: "<<buf<<endl; } close(sockfd); return 0; }
典型的UDP客户端/服务器通讯过程如下图所示:
由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,实际上有很多保证通讯可靠性的机制需要在应用层实现,可能反而会需要更多代码。
服务端步骤:1:加载套接字库,创建套接字(socket());
2:绑定套接字到一个IP地址和一个端口上(bind());
3:等待和接收数据(sendto()/recvfrom());
4:关闭套接字,关闭加载的套接字库 (close())。#include <iostream> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> using namespace std; #define MAXLINE 80 #define SERV_PORT 8888 int main() { //设置一个socket地址结构server_addr,代表服务器internet地址, 端口 struct sockaddr_in servaddr; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; //创建用于internet的数据报协议(UDP)socket,用server_socket代表服务器socket int serverfd = socket(AF_INET, SOCK_DGRAM, 0); bzero(&servaddr, sizeof(servaddr));//把一段内存区的内容全部设置为0 servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); int ret = bind(serverfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); if(ret < 0){ cout<<"fail bind"<<endl; return 0; } /* 定义一个地址,用于捕获客户端地址 */ struct sockaddr_in client_addr; socklen_t client_addr_length = sizeof(client_addr); while (1) { int n = recvfrom(serverfd,buf,sizeof(buf),0,(struct sockaddr *)&client_addr,&client_addr_length); if(n>0){ cout<<"received from "<<inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)) <<" at PORT "<<client_addr.sin_port<<":"<<buf<<endl; for(int i = 0; i < n; i++)//将从client接收到的字母转化为大写,回送给client buf[i] = toupper(buf[i]); sendto(serverfd,buf,sizeof(buf),0,(struct sockaddr *)&client_addr,sizeof(client_addr)); } } close(serverfd); return 0; }
1:创建一个套接字(socket);
2:向服务器发送数据(sendto);
3:关闭套接字;#include <iostream> #include <string.h> #include <stdio.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> using namespace std; #define MAXLINE 80 #define SERV_PORT 8888 int main(int argc, char *argv[]) { char buf[MAXLINE]; /* 服务端地址 */ sockaddr_in servaddr = {0}; servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); //定义一个client socket int client_fd = socket(AF_INET, SOCK_DGRAM, 0); cin>>buf; int count = sendto(client_fd,buf,sizeof(buf),0,(struct sockaddr *)&servaddr,sizeof(servaddr)); if(count>0){ cout<<"success send to server"<<endl; socklen_t server_add_len = sizeof(servaddr); int n = recvfrom(client_fd,buf,sizeof(buf),0,(struct sockaddr *)&servaddr,&server_add_len); if(n>0){ cout<<"response from server:"<<buf<<endl; } }else{ cout<<"fail send to server"<<endl; } close(client_fd); return 0; }
使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,protocol参数仍然指定为0即可。
UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。
下面是unix udp通信的例子:#include <iostream> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> using namespace std; #define MAXLINE 60 int main() { /* delete the socket file */ unlink("server_socket"); /* create a UNIX socket */ int serverfd = socket(AF_UNIX, SOCK_DGRAM, 0); struct sockaddr_un server_addr; server_addr.sun_family = AF_UNIX; strcpy(server_addr.sun_path, "server_socket"); /* bind with the local file */ bind(serverfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); char buf[MAXLINE]; struct sockaddr_un client_addr; socklen_t len = sizeof(client_addr); while(1) { int n = recvfrom(serverfd,buf,sizeof(buf),0,(struct sockaddr *)&client_addr,&len); if(n>0){ cout<<"received:"<<buf<<endl; } } close(serverfd); return 0; }客户端:
#include <iostream> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> using namespace std; #define MAXLINE 60 int main() { /* create a socket */ int client_fd = socket(AF_UNIX, SOCK_DGRAM, 0); /*server address*/ struct sockaddr_un servaddr; servaddr.sun_family = AF_UNIX; strcpy(servaddr.sun_path, "server_socket"); char buf[MAXLINE]; cin>>buf; int count = sendto(client_fd,buf,sizeof(buf),0,(struct sockaddr *)&servaddr,sizeof(servaddr)); if(count>0){ cout<<"send success:"<<buf<<endl; } /* close the socket */ close(client_fd); return 0; }