1. 程式人生 > 其它 >socket 遠端主機強迫關閉了一個現有的連線_C++ | socket筆記

socket 遠端主機強迫關閉了一個現有的連線_C++ | socket筆記

技術標籤:socket 遠端主機強迫關閉了一個現有的連線

socket

socket是使用標準Unix檔案描述符(file descriptor)和其它程式通訊的方式。

internet套接字的兩種型別

其實有多種型別,常用兩種是stream sockets流格式datagram sockets資料包格式。其中前者是可靠的雙向通訊的資料流,使用TCP協議;後者是無連線套接字,使用UDP協議。

涉及的結構體

套接字用到的各種資料型別

socket描述符,型別是int,當某資料必須按照NBO順序,那麼要呼叫函式從本機位元組順序轉換成網路位元組順序NBO

struct sockaddr {
    unsigned short sa_family; /* 地址家族, AF_XXX */
    char sa_data[14]; /* 14位元組協議地址 */
};

其中sa_family可以是各種各樣的型別,這裡都是使用AF_INETsa_data包含套接字中的目標地址和埠資訊。為了處理struct sockaddr,創造了並列的結構

struct sockaddr_in {
    short int sin_family; /* 通訊型別 */
    unsigned short int sin_port; /* 埠 */
    struct in_addr sin_addr; /* internet 地址 */
    unsigned char sin_zeros[8]; /* 與sockaddr結構的長度相同 */
}:

struct in_addr {
    unsigned long s_addr;
}

其中,sin_zeros應該使用bzero()或者memset()全部清零。一個指向sockaddr_in結構體的指標可以被指向結構體sockaddr並且代替它。這樣即使socket()想要的是struct sockaddr*,仍可以使用sruct sockaddr_in,並且在最後轉換。注意,sin_familysa_family一致並能夠設定為AF_INET。最後,sin_portsin_addr必須使用網路位元組順序。val.sin_addr.in_addr.s_addr儲存的是4位元組的網路位元組順序的IP地址。

本機轉換

網路和本機位元組順序的轉換,能夠轉換兩種型別,short兩位元組

long四位元組,對unsigned也適用。轉換函式名是這樣組成的,{host:h, network:n, short:s, long:l, to:to},所以所有轉換函式名的集合是{htonl, htons, ntohs, ntohl}sin_addrsin_port需要轉換成網路位元組順序,是因為兩者分別封裝在包的IP層UDP層,需要傳送到網路;sin_family只是被核心使用來決定在資料結構中包含什麼型別的地址,所以必須是本機位元組順序,而且沒有被髮送到網路,可以是本機位元組順序。

IP地址

設定sockaddr_in的IP地址的時候可以這樣設定,val.sin_addr.in_addr.s_addr = inet_addr("127.0.0.1);inet_addr返回的已經是網路位元組順序,無需轉換,若要將IP地址轉換點數格式,則使用函式inet_ntoa(),其返回的是指向一個字元的char*指標,是一個由inet_ntoa()控制的靜態的固定的指標,所以每次呼叫inet_ntoa()都會覆蓋上次呼叫時所得的IP地址。

函式介面

socket()函式

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

其中,domain設定AF_XXXtype設定SOCK_STREAM || SOCK_DGRAMprotocol設定0,函式返回以後再系統呼叫中可能用到的socket描述符,錯誤返回-1,全域性變數errno儲存返回的錯誤值。

bind()函式

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr* my_addr, int addrlen);

sockfd是呼叫socket返回的檔案描述符。my_addr是指向資料結構struct sockaddr的指標,儲存埠和IP地址資訊,addrlen設定為sizeof(struct sockaddr)。當sin_port=0時,告訴bind()自己選擇合適的埠,當s_addr=INADDR_ANY時,告訴它自動填上它所執行的機器的IP地址。注意,不要使用小於1024的埠號,所有小於1024的埠號都被系統保留,可以選擇1024~65535的埠。如果使用connect()來和遠端機器通訊,根本不需要呼叫bind(),只要簡單的呼叫connect()函式就可以,它會檢查套接字是否繫結埠,若沒有,它會自己繫結一個沒有使用的本地埠。

connect()函式

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, struct sockaddr* serv_addr, int addrlen);

sockfd是呼叫socket返回的套接字檔案描述符。serv_addr是儲存著目的地埠和IP地址的資料結構struct sockaddraddrlen設定為sizeof(struct sockaddr)。只關心要去哪,就不需要呼叫bind函式。

listen()函式

int listen(int sockfd, int backlog);

sockfd是呼叫socket()返回的套接字檔案描述符。backlog是再進入佇列中允許的連線數目,進入的連線是在佇列中一直等待直到你接受sccpet連線。加入不希望與遠端的一個地址相連,就需要等待接入請求並且用各種方法處理它們。首先listen,然後accept。順序是socket->bind->listen->accept

accpet()函式

#include <sys/socket.h>

int accpet(int sockfd, void* addr, int* addrlen);

sockfd是呼叫socket()返回的套接字檔案描述符。addr是個指向區域性的資料結構sockaddr_in的指標。這是要求接入的資訊所要去的地方。在它的地址傳遞給accept之前,addrlen是個區域性的整形變數,大小為sizeof(struct sockaddr_in)。客戶端通過一個你在listen的埠connect到你的機器,它的連線將加入到accept的佇列中。你呼叫accept告訴它你有空閒的連線,它將返回一個新的套接字檔案描述符。此時就有兩個套接字了,一個還在偵聽埠,新的在準備send和recv資料。其中send和recv需要使用新的套接字描述符。如果只想一個連線進來,則可使用close關閉原來的檔案面舒服來避免同一個埠更多的連線。

send()函式和recv()函式

int send(int sockfd, const void* msg, int len, int flags);

int recv(int sockfd, void* buf, int len, unsigned int flags);

sockfd是你想傳送資料的套接字描述符。msg是指向你想傳送的資料的指標。len是資料的長度。flags設為0。send()返回實際傳送的資料的位元組數。sockfd是你想傳送資料的套接字描述符。buf是要讀的資訊的快取。len是快取的最大長度。flags設為0。recv()返回實際讀入緩衝的資料的位元組數。

close()函式和shutdown()函式

close(sockfd);

int shutdown(int sockfd, int how);

close()會完全關閉套接字描述符。防止套接字上更多的資料的讀寫。

shutdown()允許你將一定方向上的通訊或者雙向的通訊關閉。其中0是不允許接受,1是不允許傳送,2和close一樣,不允許接受和傳送。

getpeername()函式和gethostname()函式

#include <sys/socket.h>

int getpeername(int sockfd, struct sockadddr* addr, int* addrlen);

#include <unistd.h>

int gethostname(char* hostname, size_t size);

getpeername()函式可以好書你連線的流式套接字上誰在另一邊。

gethostname()函式返回你程式所執行的機器的主機名字,然後可以使用gethostbyname獲得你機器的IP地址。

gethostbyname()函式

#include <netdb.h>

struct hostent {
    char* h_name; /* 地址的正式名稱 */
    char** h_aliases; /* 空位元組,地址的預備名稱的指標 */
    int h_addrtype; /* 地址型別,通常是AF_INET */
    int h_length; /* 地址的位元長度 */
    char** h_addr_list; /* 零位元組,主機網路地址指標,網路位元組順序 */
};

strcut hostent* gethostbyname(const char* name);

給某站點的地址,返回IP地址。

select()函式

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int numfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

多路同步I/O。巴拉巴拉巴拉

例項

server

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>

#include <unistd.h>
#include <string.h>

#define PORT 8080
#define LISTENQ 3

int main()
{
    int connfd;
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd < 0)
        std::cout << "create socket failed" << std::endl;

    sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(PORT);

    int bind_ok = bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    if(bind_ok < 0)
        std::cout << "bind socket with server address failed" << bind_ok << std::endl;

    int listen_ok = listen(listenfd, LISTENQ);
    if(listen_ok < 0)
        std::cout << "listen socket failed" << std::endl;

    std::string buffer = "hello, this is a simple server. n";

    int write_ok = 0;

    int client_st = 0;
    socklen_t len = 0;
    sockaddr_in clinet_addr;
    void* ptr = &clinet_addr;
    char s[1024];

    while(true)
    {
        bzero(&clinet_addr, sizeof(clinet_addr));
        socklen_t len = sizeof(clinet_addr);
        client_st = accept(listenfd, (struct sockaddr*)&clinet_addr, &len);
        if(client_st != 0)
            std::cout << "accept failed" << std::endl;

        while(true)
        {
            memset(s, 0, sizeof(1024));

            int rc = recv(client_st, s, sizeof(s), 0);
            if(rc > 0)
            {
                std::cout << "receive success " << (std::string)(s) << std::endl;

            }
            else if(rc == 0)
            {
                std::cout << "receive close" << std::endl;
                break;
            }
            else
            {
                std::cout << "recieve failed";
                break;
            }

            std::string str = "this is server";
            int send_ok = send(client_st, str.c_str(), strlen(str.c_str()), 0);
            if(send_ok == -1)
                std::cout << "server send failed" << std::endl;
        }
        close(client_st);
    }

    close(connfd);

    return 0;
}

client

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>

#include<arpa/inet.h>

#include <unistd.h>
#include <string.h>

#define PORT 8080

int main()
{
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in client_addr;
    bzero(&client_addr, sizeof(client_addr));

    client_addr.sin_family = AF_INET;
    client_addr.sin_port = htons(PORT);
    client_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int connfd = connect(listenfd, (struct sockaddr*)&client_addr, sizeof(client_addr));
    if(connfd == -1)
        std::cout << "connect failed" << std::endl;

    std::string str = "this is client";

    int send_ok = send(listenfd, str.c_str(), strlen(str.c_str()), 0);
    if(send_ok == -1)
        std::cout << "client send failed" << std::endl;

    char s[1024];
    memset(s, 0, sizeof(1024));
    int rc = recv(listenfd, s, sizeof(s), 0);
    if(rc > 0)
        std::cout << "receive success " << (std::string)(s) << std::endl;
    else if(rc == 0)
        std::cout << "receive close" << std::endl;
    else
        std::cout << "recieve failed";

    close(listenfd);
    return 0;
}