Linux下的Socket编程大体上包括Tcp Socket、Udp Socket即Unix Domain Socket这三种,其中TCP和UDP方式的Socket编程用于编写应用层的socket程序,是我们用得比较多的,Unix Domain Socket主要用于unix的本地通信。
TCP Socket
基于TCP协议的客户端/服务器程序的一般流程一般如下:
它基本上可以分为三个部分:
一、建立连接:
-
服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态
-
客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答
-
服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。
二、传输数据:
建立连接后,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 Socket
典型的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 Socket
socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIXDomain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。使用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;
}