- 协议
TCP(传输控制协议),UDP(用户数据包协议)为传输层重要的两大协议,向上为HTTP提供底层协议,向下为数据链路层封装底层接口,乃是通信重中之重。TCP是面向流传输的协议,在编程中形象化为Stream,如流水一般,读入读出。流的基本单位为byte。而UDP则为数据包协议,以数据包为单位。协议的细节不再赘述,本次提供两种协议的最基础套接字编程模型。
- API
服务端最基本的流程:新建套接字->绑定端口->开始监听....->建立连接->传输数据->关闭连接
客户端最基本的流程:新建套接字->连接...->传输数据->关闭连接
#服务端#这是新建套接字的API 第一个参数ARPA Internet地址格式,第二个参数是使用流协议,第三个参数是指定协议,并未遇到应用场景 通常 传0int server_socketfd = socket(PF_INET,SOCK_STREAM,0)#这是绑定函数 绑定已申请的socket描述符到端口上,第二个参数是ip地址结构体指针,第三个是地址描述结构体的长度bind(server_socketfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr)#接受连接函数 意味开始监听 第一个参数是服务器套接字 第二个参数返回的是客户端连接的地址 第三个是地址长度accept(server_socketfd, (struct sockaddr *)&remote_addr,&sin_size)#客户端#客户端连接函数 第一个参数是socket对象描述符,第二个是ip地址,第三个是ip地址的长度connect(client_fd,(struct sockaddr *)&remote_addr, sizeof(struct sockaddr))#io#发送函数 第一个是发送的套接字描述符 第二个是内容 第三个是发送长度 第四个是flags 一般0send(client_socketfd,"welcome to login\n",21,0)
#接收函数 接受client对象的数据到buf中,一次接受bufferSize个长度,最后一个参数是flags 和send对应 recv(client_socketfd, buf, bufferSize, 0)
- 完整程序
为了方便测试,我使用了C++和Java做一对交互程序,C++做服务器的时候就用Java做客户端,反之也有。
Linux socket API存在头文件sys/socket.h中,并且期间使用多个头文件的函数,大概使用的头文件如下。
#include#include #include #include #include #include #include
TCP服务端Demo
#ifndef COMMUNICAITON_TCPSOCKET_H#define COMMUNICAITON_TCPSOCKET_H#include#include #include #include #include #include #include #endif //COMMUNICAITON_TCPSOCKET_Hvoid tcpServerStart();#include "TCPSocket.h"const int bufferSize = 1024;void tcpServerStart(){ printf("server socket start init...\n"); int server_socketfd;//服务器套接字 int client_socketfd;//客户端套接字 int port = 8000; struct sockaddr_in my_addr;//服务器网络地址结构 struct sockaddr_in remote_addr;//虚拟网络地址结构 socklen_t sin_size;//此处须运用长度定义类型 socketlen_t char buf[bufferSize];//数据缓冲区 long sendLen = 0;//发送接收长度 memset(&my_addr,0,sizeof(my_addr));//数据初始化 my_addr.sin_family=AF_INET;//设置为IP通信 my_addr.sin_addr.s_addr=INADDR_ANY;//服务器ip my_addr.sin_port=htons(port);//设置端口为8000 /*创建服务器端套接字--IPv4协议,TCP协议*/ if((server_socketfd = socket(PF_INET,SOCK_STREAM,0))<0){ perror("socket init error"); return ; } /*将套接字绑定到服务器的网络地址上*/ if(bind(server_socketfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0){ perror("bind socket error"); return; } printf("server socket start listen ,port:%d",port); /*监听连接请求--监听队列长度为5*/ listen(server_socketfd,5); sin_size = sizeof(struct sockaddr_in); /*等待客户端连接请求到达*/ client_socketfd=accept(server_socketfd, (struct sockaddr *)&remote_addr,&sin_size); if(client_socketfd < 0){ perror("listen error"); return; } printf("accept %s",inet_ntoa(remote_addr.sin_addr));//打印客户端ip地址 sendLen=send(client_socketfd,"welcome to login\n",21,0);//发送欢迎信息 if(sendLen <= 0){ perror("no send"); return; } while(sendLen=recv(client_socketfd, buf, bufferSize, 0)>0){ printf("accept msg==="); printf("%s/n",buf); } close(client_socketfd); close(server_socketfd);}
UDP服务端Demo
#ifndef COMMUNICAITON_UDPSOCKET_H#define COMMUNICAITON_UDPSOCKET_H#include#include #include #include #include #include #include #endif //COMMUNICAITON_UDPSOCKET_Hvoid udpServerStart();
#include "UDPSocket.h"const int bufSize = 1024;void udpServerStart(){ printf("udp server init...\n"); int server_sockfd; int len; int port = 8000; struct sockaddr_in my_addr; struct sockaddr_in remote_addr; socklen_t sin_size;//此处使用其自定义的socklen属性 char buf[bufSize]; memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零 my_addr.sin_family = AF_INET; my_addr.sin_addr.s_addr = INADDR_ANY; my_addr.sin_port = htons(port); /*创建服务器端套接字--IPv4协议,面向无连接通信,UDP协议*/ if((server_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0){ perror("create socket error"); return; } /*将套接字绑定到服务器的网络地址上*/ if(bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0){ perror("bind socket error"); return; } sin_size = sizeof(struct sockaddr_in); printf("waiting for package"); /*接收客户端的数据并将其发送给客户端--recvfrom是无连接的*/ if((len=recvfrom(server_sockfd,buf,bufSize,0,(struct sockaddr *)&remote_addr,&sin_size))<0){ perror("recv error"); return; } printf("received packet from %s:\n",inet_ntoa(remote_addr.sin_addr)); printf("contents: %s\n",buf); close(server_sockfd);}
其中有某些函数调用非socket API中,比如memset,inet_ntoa等。
TCP客户端 Demo
#ifndef COMMUNICAITON_TCPCLIENT_H#define COMMUNICAITON_TCPCLIENT_H#include#include #include #include #include #include #include #endif //COMMUNICAITON_TCPCLIENT_Hvoid tcpClient();#include "TCPClient.h"const int bufSize = 1024;void tcpClient(){ int client_fd; int len; struct sockaddr_in remote_addr; char buf[bufSize]; memset(&remote_addr,0, sizeof(remote_addr)); remote_addr.sin_family=AF_INET; remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1"); remote_addr.sin_port=htons(8000); /*创建客户端套接字--IPv4协议,面向连接通信,TCP协议*/ if((client_fd=socket(PF_INET,SOCK_STREAM,0))<0){ perror("socket error"); return; } /*将套接字绑定到服务器的网络地址上*/ if(connect(client_fd,(struct sockaddr *)&remote_addr, sizeof(struct sockaddr))<0){ perror("connect error"); return; } printf("connected to server\n"); len=recv(client_fd,buf,bufSize,0); printf("get msg :%s",buf); close(client_fd);}
UDP客户端 Demo
#ifndef COMMUNICAITON_UDPCLIENT_H#define COMMUNICAITON_UDPCLIENT_H#include#include #include #include #include #include #include #endif //COMMUNICAITON_UDPCLIENT_Hvoid udpClient();/* * 向本地(localhost) 8000端口发送一个数据包 */const int buffSize = 1024;void udpClient(){ int client_sockfd; int len ; struct sockaddr_in remote_addr; int sin_size; char buf[buffSize]; memset(&remote_addr,0, sizeof(remote_addr)); remote_addr.sin_family=AF_INET; remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1"); remote_addr.sin_port=htons(8000); /*创建客户端套接字--IPv4协议,面向无连接通信,UDP协议*/ if((client_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0) { perror("socket not found"); return; } stpcpy(buf,"this is a C++ udp client"); printf("send msg :%s\n",buf); sin_size= sizeof(struct sockaddr_in); /*向服务器发送数据包*/ if((len=sendto(client_sockfd,buf,strlen(buf),0,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr)))<0){ perror("send error"); return; } close(client_sockfd);}
用于对应的Java客户端/服务端(比较熟悉Java,用于测试)
public class TCPClient { public void tcpConnectReacServerFirst(String addr,int port){ Socket socket = null; try{ socket = new Socket(addr,port); InputStream ins = socket.getInputStream(); OutputStream ous = socket.getOutputStream(); BufferedReader bufReader = new BufferedReader(new InputStreamReader(ins)); String msg = bufReader.readLine(); System.out.println("接到服务器信息:"+msg); ous.write("hello server\n".getBytes()); ous.flush(); ins.close(); ous.close(); }catch (Exception e){ e.printStackTrace(); }finally { if(socket != null){ if(socket.isClosed()){ try{ socket.close(); }catch (Exception e){ e.printStackTrace();; } } } } }}
public class UDPClient { public void udpSender(String addr,int port){ byte[] msg = "this is udp sender\n".getBytes(); try{ //数据包 //数据包byte数组 数组长度 包目的ip地址 端口 DatagramPacket datagramPacket = new DatagramPacket(msg,msg.length,InetAddress.getByName(addr),port); DatagramSocket socket = new DatagramSocket(); socket.send(datagramPacket); if(!socket.isClosed()){ socket.close(); } }catch (Exception e){ e.printStackTrace(); } }}
public class TCPServer { public void tcpServerStart(int port){ ServerSocket server = null; final String welcome = "welcome to login,Java TCP server!\n"; try{ server = new ServerSocket(port); Socket socket = server.accept(); OutputStream ous = socket.getOutputStream(); ous.write(welcome.getBytes()); ous.close(); socket.close(); server.close(); }catch (Exception e){ e.printStackTrace(); } }}
public class UDPServer { public void udpStartup(int port,int byteCount){ System.out.println("udp server start ,port:"+port); try{ InetAddress inet = InetAddress.getLoopbackAddress(); DatagramSocket udp = new DatagramSocket(port); byte[] buf = new byte[byteCount]; DatagramPacket packet = new DatagramPacket(buf,buf.length); udp.receive(packet); String getMsg = new String(buf,0,packet.getLength()); System.out.println("收到客户端 "+packet.getAddress().getHostAddress()+" 发来信息:"+getMsg); udp.close(); }catch (Exception e){ e.printStackTrace(); } }}
- 体会
C++在unix中形成了一种对对象的描述,或为句柄、或为fd,将其理解为Java中的socket对象类比一下,就容易理解了。而传入地址以及长度,应该是操作系统需要新建空间申请内存而用。总而言之,在传统通信程序中,socket的模型就是连接对象,其最大的应用就是如下
while(true){ socket = accept(); Thread handler = new Handler(socket); handler.start();}
为此基础上优化,就是添加线程池,或者加请求队列。这种模式逻辑明了,编码简单,是最容易理解的编程模型。