1. 程式人生 > >Linux 套接字通信筆記(一)

Linux 套接字通信筆記(一)

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 套接字通信筆記(一)