socket 遠端主機強迫關閉了一個現有的連線_C++ | 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_INET
,sa_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_family
和sa_family
一致並能夠設定為AF_INET
。最後,sin_port
和sin_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_addr
和sin_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_XXX
,type
設定SOCK_STREAM || SOCK_DGRAM
,protocol
設定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 sockaddr
。addrlen
設定為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;
}