網絡編程學習筆記:linux下的socket編程

socket是進程通訊的一種方式,經過調用一些API能夠實現進程間通訊,創建鏈接以及收發信息的過程以下圖所示:ios

這些函數的用法以下:ubuntu

一、int socket(int protocolFamily, int type, int protocol); 返回描述符sockfd緩存

  • l  protocolFamily:協議族,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或稱AF_UNIX,unix域socket)、AF_ROUTE等。協議族決定了socket的地址類型,在通訊中必須採用對應的地址,如AF_INET決定了用IPV4地址(32位)與端口號(16位),AF_UNIX決定了要用一個絕對路徑名做爲地址
  • l  type:指定socket類型。經常使用的類型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等
  • l  protocol:協議名

調用socket建立以後,返回的描述符存在於協議族空間中,但沒有一個具體的地址,必需要經過bind才行服務器

二、int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);socket

  • l  sockfd:socket描述字,socket的返回值
  • l  addr:一個const struct *addr指針,指向要綁定的sockfd的協議地址。這個地址結構根據地址建立socket時的地址協議族的不一樣而不一樣

如, IPV4:函數

struct sockaddr_in {
sa_family_t    sin_family; /* address family: AF_INET */
in_port_t      sin_port;   /* port in network byte order */
struct in_addr sin_addr;   /* internet address */
};

struct in_addr{
  uint32_t s_addr; // address in network byte order
};
  • l  addrlen:對應的地址的長度

三、int listen(int sockfd, int backlog);服務器監聽函數ui

  • l  sockfd:socket描述字,socket的返回值
  • l  backlog: socket 能夠排隊的最大鏈接個數

  listen函數將socket變爲被動類型的, 等待客戶鏈接請求spa

四、int connect(int sockfd, const struct *addr, socklen_t addrlen);3d

  • l  sockfd:socket描述字,socket的返回值
  • l  addr:服務器的socket地址
  • l  addrlen:socket地址的長度

五、int accept(int sockfd, struct sockaddr* addr, socklen_t *addrlen);unix

  • l  sockfd:socket描述字,socket的返回值
  • l  addr:結果參數,接收一個返回值指向客戶端的地址,若是對客戶的地址不在意,能夠設置爲NULL
  • l  addrlen:結果參數,接收上述addr結構大小,指明addr結構所佔字節數,也可NULL

  accept()成功返回一個SOCKET描述符,表示接收到的套接字的描述符。不然返回值INVALID_SOCKET。

六、ssize_t send(int sockfd, const void *buf, ssize_t len, int flags);

  • l  sockfd: socket描述字,socket的返回值
  • l  buf:要發送的數據buffer
  • l  len:要傳送的數據大小
  • l  flag:通常取值爲0,影響TCP首部的可選部分

  send將本身的數據copy到內核的send buffer,返回值爲[-1,size]:

    • 返回-1說明發送數據失敗,系統內部出問題了
    • 返回[0,size]:因爲send是從內核的send buffer中寫數據,那麼send buffer中剩下的長度爲m,返回min(m, size),返回0就是send buffer沒有空間了

七、ssize_t recv(int sockfd, void *buf, ssize_t len, int flags);

各參數意義基本與send相同,recv操做將內核中的數據拷貝到應用程序內部

返回值是[-1,size]:

    • 返回-1代表接收失敗,socket失效、recv操做因爲系統內部緣由中斷等緣由
    • 返回0代表沒有數據,在TCP中接收發送有一個timeout,當timeout的時候尚未數據返回0
    • 返回[1,size]:recv的操做時從內核拷貝數據,數據有多少拷貝多少,內核如今收到長度爲m的數據,返回min(m, size);

八、int close(int fd);

 

一個echo小例子,客戶端向服務器發送什麼服務器就給客戶端返回什麼信息:

一、服務器代碼:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>   // 基本系統數據類型,是系統的基本系統數據類型的頭文件
#include<sys/socket.h>  
#include<netinet/in.h>  // 互聯網地址族
#include<arpa/inet.h>   //IP地址轉換函數inet_pton
#include<unistd.h>      //close

#include<iostream>

int main(int argc, char** argv){
    int socketfd, bindfd, connectfd;
    char buffer[4096];
    struct sockaddr_in serverAddress;
    int sendSize, recvSize;

    //printf("================== create socket ======================\n");
    // create socket
    socketfd = socket(AF_INET, SOCK_STREAM, 0);
    if(socketfd == -1){
        printf("Create socket error:%s(errno:%d)\n", strerror(errno),errno);
        exit(0);
    }

    //printf("================== set address ========================\n");
    //set server address
    memset(&serverAddress, 0, sizeof(serverAddress));
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(800);
    //serverAddress.sin_addr.s_addr = htonl(127.0.0.1);
    inet_pton(AF_INET, "127.0.0.1", &serverAddress.sin_addr);

    std::cout << serverAddress.sin_addr.s_addr << '\n';

    //printf("================== bind address ========================\n");
    // bind address to the socket
    bindfd = bind(socketfd, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
    if(bindfd == -1){
        printf("bind socket error:%s(errno:%d)\n", strerror(errno),errno);
        exit(0);        
    }

    //printf("================== listen ========================\n");
    if(listen(socketfd, 10) == -1){
        printf("listen socket error:%s(errno:%d)\n",strerror(errno),errno);
        exit(0);
    }

    printf("================== waiting connect ========================\n");
    while(1){
        sleep(2);
        // recvive a connect and accept
        connectfd = accept(socketfd, (struct sockaddr*)NULL, NULL);
        if(connectfd == -1){
            printf("connet socket error:%s(errno:%d)\n",strerror(errno),errno);
            continue;        
        }

        // receive data
        recvSize = recv(connectfd, buffer, 4096, 0);
        if(recvSize == -1){
            printf("recvive data error:%s(errno:%d)\n",strerror(errno),errno);
            continue;            
        }
        
        printf("%s\n", buffer);

        // send data
        sendSize = send(connectfd, buffer, 4096, 0);
        if(sendSize == -1){
            printf("send data error:%s(errno:%d)\n",strerror(errno),errno);
            continue;            
        }
        close(connectfd);

    }
    close(socketfd);


}

二、客戶端代碼:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>   // 基本系統數據類型,是系統的基本系統數據類型的頭文件
#include<sys/socket.h>  
#include<netinet/in.h>  // 互聯網地址族
#include<arpa/inet.h>   //IP地址轉換函數inet_pton
#include<unistd.h>      //close

#include<iostream>

int main(){
    int socketfd, connectfd;
    int sendSize, recvSize;

    struct sockaddr_in serverAddress;

    char sendBuf[4096];
    char recvBuf[4096];

    printf("================== create socket ========================\n");
    socketfd = socket(AF_INET, SOCK_STREAM, 0);
    if(socketfd == -1){
        printf("create socket error:%s(error%d)\n", strerror(errno),errno);
        exit(0);
    }

    memset(&serverAddress, 0, sizeof(serverAddress));
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(800);
    //serverAddress.sin_addr.s_addr = htonl("127.0.0.1");    
    inet_pton(AF_INET, "127.0.0.1", &serverAddress.sin_addr);

    std::cout << serverAddress.sin_addr.s_addr << '\n';

    connectfd = connect(socketfd, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
    if(connectfd == -1){
        printf("connect server error:%s(error(%d))\n", strerror(errno), errno);
        exit(0);
    }

    printf("send message to server:\n");
    //std::cin >> sendBuf;
    fgets(sendBuf, 4096, stdin);
    //printf("%s\n", sendBuf);
    sendSize = send(socketfd, sendBuf, strlen(sendBuf), 0);
    if(sendSize == -1){
        printf("send data error:%s(error%d)\n", strerror(errno), errno);
        exit(0);
    }

    printf("wait echo from server:\n");

    sleep(2);
    recvSize = recv(socketfd, recvBuf, sizeof(recvBuf), 0);
    if(recvSize < 0){
        printf("recvive echo error:%s(error%d)\n", strerror(errno), errno);
        exit(0);
    }
    printf("%s", recvBuf);

    close(socketfd);

}

在這過程當中遇到的幾個問題:

一、(客戶端)errno 111:connection refused

  這個問題說明客戶端沒有找到應該鏈接的端口,須要作到:

  • 確保服務端在相應的端口監聽;
  • 關閉防火牆(ubuntu下面的命令:sudo ufw disable);
  • 並且server端要 sudo 運行;

  因爲我客戶端和服務器端口號不匹配,就出現了這個問題

二、(服務器)errno 14:bad address

  accept()函數的第二個參數指的是一個接收返回結果的緩存區,表示的是本次鏈接的客戶端的地址,若是無所謂客戶端地址能夠寫爲NULL,而一旦給這個參數賦了非NULL得值,可是這個緩存區又不可寫的話,就會出現bad address報錯

三、(服務器)errno 107: transport endpoint is not connected.

  這個問題,是因爲我服務器端的recv和send函數的第一個參數寫的本地socket描述符形成的,事實上,這應該是已鏈接的socket描述符

相關文章
相關標籤/搜索