Linux 套接字通信筆記(一)
阿新 • • 發佈:2018-02-10
ufs 編程 stdio.h conn 發送 對象 pcl tcpclient java客戶端
- 協議
TCP(傳輸控制協議),UDP(用戶數據包協議)為傳輸層重要的兩大協議,向上為HTTP提供底層協議,向下為數據鏈路層封裝底層接口,乃是通信重中之重。TCP是面向流傳輸的協議,在編程中形象化為Stream,如流水一般,讀入讀出。流的基本單位為byte。而UDP則為數據包協議,以數據包為單位。協議的細節不再贅述,本次提供兩種協議的最基礎套接字編程模型。
- API
服務端最基本的流程:新建套接字->綁定端口->開始監聽....->建立連接->傳輸數據->關閉連接
客戶端最基本的流程:新建套接字->連接...->傳輸數據->關閉連接
#服務端 #這是新建套接字的API 第一個參數ARPA Internet地址格式,第二個參數是使用流協議,第三個參數是指定協議,並未遇到應用場景 通常 傳0 int 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 一般0 send(client_socketfd,"welcome to login\n",21,0)
- 完整程序
為了方便測試,我使用了C++和Java做一對交互程序,C++做服務器的時候就用Java做客戶端,反之也有。
Linux socket API存在頭文件sys/socket.h中,並且期間使用多個頭文件的函數,大概使用的頭文件如下。
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <memory.h>
TCP服務端Demo
#ifndef COMMUNICAITON_TCPSOCKET_H #define COMMUNICAITON_TCPSOCKET_H #include <stdio.h> #include <memory.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #endif //COMMUNICAITON_TCPSOCKET_H void 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 <stdio.h> #include <memory.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #endif //COMMUNICAITON_UDPSOCKET_H void 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 <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <memory.h> #endif //COMMUNICAITON_TCPCLIENT_H void 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 <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <memory.h> #endif //COMMUNICAITON_UDPCLIENT_H void 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(); }
為此基礎上優化,就是添加線程池,或者加請求隊列。這種模式邏輯明了,編碼簡單,是最容易理解的編程模型。
Linux 套接字通信筆記(一)