日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux socket API

發(fā)布時間:2023/11/16 linux 70 coder
生活随笔 收集整理的這篇文章主要介紹了 Linux socket API 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

socket是進程通信機制的一種,與PIPE、FIFO不同的是,socket即可以在同一臺主機通信(unix domain),也可以通過網(wǎng)絡在不同主機上的進程間通信(如:ipv4、ipv6),例如因特網(wǎng),應用層通過調(diào)用socket API來與內(nèi)核TCP/IP協(xié)議棧的通信,通過網(wǎng)絡字節(jié)實現(xiàn)不用主機之間的數(shù)據(jù)傳輸。

前置條件

字節(jié)序

對于多字節(jié)的數(shù)據(jù),不同處理器存儲字節(jié)的順序稱為字節(jié)序,主要有大端序(big-endian)和小端序(little-endian),字節(jié)序的收發(fā)不統(tǒng)一就會導致值被解析錯誤。

大端序

高位字節(jié)存低位內(nèi)存

大端序是最高位字節(jié)存儲在最低位內(nèi)存地址處。例如一段數(shù)據(jù)0x0A0B0C0D,0x0A是最高位字節(jié),0x0D是最地位字節(jié),內(nèi)存地址最低位a、最高位a+3,在大端序中存儲方式如下

  • 8bit存儲方式:內(nèi)存地址從低到高0x0A -> 0x0B -> 0x0C -> 0x0D
  • 16bit存儲方式:內(nèi)存地址從低到高0x0A0B -> 0x0C0D

小端序

低位字節(jié)存低位內(nèi)存

小端序是最低位字節(jié)存儲在最低位內(nèi)存地址處。例如一段數(shù)據(jù)0x0A0B0C0D,0x0A是最高位字節(jié),0x0D是最地位字節(jié),內(nèi)存地址最低位a、最高位a+3,在小端序存儲方式如下

  • 8bit存儲方式:內(nèi)存地址從低到高0x0D->0X0C->0X0B->0X0A
  • 16bit存儲方式:內(nèi)存地址從低到高0X0C0D->0X0A0B

主機通常使用小端序,因為計算機先處理小端序的字節(jié)效率更高。通過上面的結(jié)構不難看出,大端序更易讀,所以網(wǎng)絡和存儲等采用了大端序,那么網(wǎng)絡通信的時候就需要將網(wǎng)絡字節(jié)的大端序轉(zhuǎn)換為主機字節(jié)的小端序。好在這些都有系統(tǒng)調(diào)用可以保證~
判斷主機的字節(jié)序:

#include <iostream>
using namespace std;
void byteorder() {
  union {
    short value;
    char union_bytes[sizeof(short)];
  } test;
  test.value = 0x0102;
  if ((test.union_bytes[0] == 0x01) && (test.union_bytes[1] == 0x02)) {
    cout << "big endian" << endl;  // [0x01, 0x02]
  } else if ((test.union_bytes[0] == 0x02) && (test.union_bytes[1] == 0x01)) {
    cout << "little endian" << endl;  // [0x02, 0x01]
  } else {
    cout << "unknow~" << endl;
  }
}

int main() { byteorder(); }

字節(jié)序轉(zhuǎn)換

#include<netinet/in.h>
// long型主機字節(jié)序轉(zhuǎn)換為long型網(wǎng)絡字節(jié)序, host to network
unsigned long int htonl(unsigned long int hostlong);
// short型
unsigned short int htons(unsigned short int hostshort);
// long型網(wǎng)絡字節(jié)序轉(zhuǎn)換為long型主機字節(jié)序, network to host
unsigned long int ntohl(unsigned long int netlong);
// short型
unsigned short int ntohs(unsigned short int netshort);

比方轉(zhuǎn)換主機的端口

int main(int argc, char *argv[]){
    int port = atoi(argv[1]); // 主機序
    server_address.sin_port = htons(port); // 網(wǎng)絡序
}

地址

通用地址

地址我們標識通信的端點,通用的地址格式為

#include<bits/socket.h>
struct sockaddr
{
    sa_family_t sa_family; // 協(xié)議類型,例如 ipv4 AF_INET、unix AF_UNIX
    char sa_data[14]; // unix域存放文件路徑,ip域存放ip地址和端口號
}

sa_data只能容納14字節(jié)地址數(shù)據(jù),如果是unix域路徑長度可以達到108字節(jié)放不下,所以linux定義了新的地址

#include<bits/socket.h>
struct sockaddr_storage
{
    sa_family_t sa_family;
    unsigned long int__ss_align; // 作用是內(nèi)存對齊
    char__ss_padding[128-sizeof(__ss_align)];
}

專有地址

專有地址在bind、accept、connect等需要用到的函數(shù)中需要強制轉(zhuǎn)換為通用地址,例如:(struct sockaddr *)&server_address

顧名思義專門為ipv4、unix、ipv6設計的不同socket地址結(jié)構,以ipv4為例

struct sockaddr_in
{
    sa_family_t sin_family; // AF_INET
    u_int16_t sin_port; // 網(wǎng)絡字節(jié)序的端口號
    struct in_addr sin_addr; // IP地址
};
struct in_addr
{
    u_int32_t s_addr; // 網(wǎng)絡字節(jié)序的IP地址
};

具體這樣用:

int main(int argc, char *argv[]) {
    const char *ip = argv[1]; // 主機序ip地址
    int port = atoi(argv[2]); // 主機序端口
    struct sockaddr_in address; // ipv4專有地址
    // 設置專有地址的成員
    address.sin_family = AF_INET;
    address.sin_port = htons(port);
	// 將點分10進制的ip字符串轉(zhuǎn)換為網(wǎng)絡字節(jié)序整形表示的ip地址,存入sin_addr
    inet_pton(AF_INET, ip, &address.sin_addr);
  	int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 創(chuàng)建socket
    // 綁定端口,要強制轉(zhuǎn)換為通用地址 (struct sockaddr *)&address
  	int ret = bind(sockfd, (struct sockaddr *)&address, sizeof(address));
}

創(chuàng)建連接

創(chuàng)建socket

Linux一切皆文件,所以socket創(chuàng)建好之后就是一個文件描述符,對該fd讀寫關閉、屬性控制。

以ipv4為例

#include <sys/socket.h>
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  • 第一個參數(shù)domain指定協(xié)議族,AF_INET、AF_UNIX、AF_INET6
  • 第二個參數(shù)type指定socket類型,TCP\UDP分別使用流式SOCK_STREAM和數(shù)據(jù)報式SOCK_DGRAM
  • 第三個參數(shù)protocal指定協(xié)議,有IPPROTO_TCP、IPPROTO_ICMP、IPPROTO_UDP等。通常使用默認的0。例如domain為AF_INET,type為SOCK_STREAM,那么就意味著ipv4 TCP類型的socket,protocal設置為0即可。

標識socket:bind

標識該socket,對于ipv4用ip地址和端口作為端點的表示

int ret = bind(sockfd, (struct sockaddr *)&address, sizeof(address));

成功返回0,失敗返回-1并設置errno,例如errno

  • EACCES:沒有權限綁定該端口
  • EADDRINUSE:綁定一個沒有釋放的端口和地址,通常被處于TIME_WAIT的連接使用,需要使用SO_REUSEADDR來復用處于TIME_WAIT連接的端口和地址

監(jiān)聽socket:listen

開始監(jiān)聽,并指定連接數(shù)

#include<sys/socket.h>
int listen(int sockfd,int backlog);

ret = listen(sock, 5);
  • backlog參數(shù)表示處于ESTABLISHED狀態(tài)的連接數(shù)(我的ubuntu20.4測試為backlog+1),超過該值客戶端收到ECONNREFUSED或者客戶端TIMEOUT

接受連接:accept

從listen隊列中拿連接過來,不管該連接是ESTABLISED還是CLOSE_WAIT的狀態(tài)。

int connfd = accept(sockfd, (struct sockaddr *)&client, &client_addrlength);

發(fā)起連接:connect

connect(sockfd, (struct sockaddr *)&server_address, sizeof(server_address))

成功返回0,失敗返回-1并設置errno

  • ECONNREFUSED:目標端口不存在或連接被拒絕
  • ETIMEOUT:連接超時

關閉連接

close

關閉socket fd,默認情況下:如果是多進程,fork后會將fd引用計數(shù)加1,如果要關閉該socket,父子進程都需要close,而且是同時關閉讀和寫。可以通過setsockopt的SO_LINGER控制close的行為

#include<sys/socket.h>
struct linger
{
	int l_onoff; // 關閉控制
	int l_linger; // 控制時間
}

close可能會有三種行為:

  1. l_onoff:關閉時(值為0),close默認行為,發(fā)送緩沖區(qū)所有數(shù)據(jù)后關閉連接
  2. l_onoff:打開時(值大于0),若l_linger為0,close系統(tǒng)調(diào)用立即返回,緩沖區(qū)數(shù)據(jù)被丟棄,給對端發(fā)送RST報文
  3. l_onoff:打開時(值大于0),若l_linger大于0:
    1. 阻塞型socket,close等待l_linger的時間,直到發(fā)送完緩沖區(qū)數(shù)據(jù)并收到對端的ACK,如果這段時間沒有發(fā)送完緩沖區(qū)數(shù)據(jù)并收到確認,close將返回-1并設置errno為EWOULDBLOCK。
    2. 非阻塞型socket,立即返回,根據(jù)返回值和errno來判斷殘留數(shù)據(jù)是否發(fā)送完畢

shutdown

#include<sys/socket.h>
int shutdown(int sockfd,int howto);

不引用計數(shù)直接關閉,howto參數(shù):

  • SHUT_RD:程序不能再對socketfd做讀操作,接收緩沖區(qū)數(shù)據(jù)被丟掉
  • SHUT_WR:關閉socketfd寫,緩沖區(qū)數(shù)據(jù)會在關閉前發(fā)送出去,寫操作不可執(zhí)行(半關閉狀態(tài))
  • SHUT_RDWR:同時關閉

數(shù)據(jù)讀寫

除了默認對文件描述符的read、write操作之外,socket提供了專門的讀寫數(shù)據(jù)函數(shù)

TCP讀寫(recv & send)

#include<sys/socket.h>
// recv成功時返回讀取到的長度,實際長度可能小于len
// 發(fā)生錯誤返回-1設置errno,返回0表示連接關閉
ssize_t recv(int sockfd, void*buf, size_t len, int flags);

// 成功時返回寫入的數(shù)據(jù)的長度,失敗返回-1這是errno
ssize_t send(int sockfd, const void*buf, size_t len, int flags);

flags提供了一些選項設置:

  • MSG_OOB(recv&send):發(fā)送或接收緊急數(shù)據(jù),也叫帶外數(shù)據(jù),在傳輸層的七七八八中首部信息中有說,在URG標志位1時該字段有效,seq + Urgen Pointer - 1的這一個字節(jié)是緊急數(shù)據(jù)(緊急數(shù)據(jù)只有一個字節(jié)),例如:
char buffer[1024];
memset(buffer, '\0', 1024);

// 發(fā)送端發(fā)送帶外數(shù)據(jù)hello
const char *oob_data = "hello";
send(sockfd, oob_data, strlen(oob_data), MSG_OOB);

ret = recv(connfd, buffer, BUFESIZE - 1, 0);
// 接收到hell
ret = recv(connfd, buffer, BUFESIZE - 1, MSG_OOB); // 接收端接收帶外數(shù)據(jù)
// 接收到o

hell為正常數(shù)據(jù),o為帶外數(shù)據(jù),只有最后一個字節(jié)會被認為是帶外數(shù)據(jù),前面的是正常數(shù)據(jù)。正常數(shù)據(jù)的接收會被帶外數(shù)據(jù)截斷。

  • int sockatmark(int sockfd);可以判斷下一個數(shù)據(jù)是不是帶外數(shù)據(jù),1為是,此時可以利用MSG_OOB標志的recv調(diào)用來接收帶外數(shù)據(jù)。
  • 通過SIGUSR信號觸發(fā)對帶外數(shù)據(jù)的處理
  • MSG_DONTWAIT(recv&send):對socket的此次send或recv是非阻塞操作(相當于使用O_NONBLOCK)
  • MSG_WAITALL(recv):一直讀取到請求的數(shù)據(jù)全部返回后recv函數(shù)返回

UDP讀寫(recvfrom & sendto)

通常這兩個函數(shù)用于無連接的套接字,如果用于有連接的讀寫可以把后兩位置為NULL

#include <sys/socket.h>
// 可以接收UDP,也可以接收TCP(后兩個參數(shù)置位NULL,因為TCP是面向連接的)
ssize_t recvfrom(int sockfd,void* buf,size_t len,int flags,
				struct sockaddr* src_addr,socklen_t* addrlen);
// 可以接收UDP,也可以接收TCP(后兩個參數(shù)置位NULL,因為TCP是面向連接的)
ssize_t sendto(int sockfd,const void* buf,size_t len,int flags,
				const struct sockaddr* dest_addr,socklen_t addrlen);

更高級的讀寫(recvmsg & sendmsg)

使用sendmsg可以將多個緩沖區(qū)的數(shù)據(jù)合并發(fā)送
使用recvmsg可以將接收的數(shù)據(jù)送入多個緩沖區(qū),或者接收輔助數(shù)據(jù)

#include<sys/socket.h>
ssize_t recvmsg(int sockfd,struct msghdr* msg,int flags);
ssize_t sendmsg(int sockfd,struct msghdr* msg,int flags);

msghdr結(jié)構

struct msghdr
{
    void* msg_name; // socket地址,如果是流數(shù)據(jù),設置為NULL
    socklen_t msg_namelen; // 地址長度
    struct iovec* msg_iov; // I/O緩存區(qū)數(shù)組,分散的緩沖區(qū)
    int msg_iovlen; // I/O緩存區(qū)數(shù)組元素數(shù)量
    void* msg_control; // 輔助數(shù)據(jù)起始位置
    socklen_t msg_controllen; // 輔助數(shù)據(jù)字節(jié)數(shù)
    int msg_flags; // 等于recvmsg和sendmsg的flags參數(shù),在調(diào)用過程中更新
};

輔助函數(shù)

獲取地址

#include<sys/socket.h>
// 獲取socketfd本端的地址信息,存到address,如果address長度大于address_len,將被截斷
int getsockname(int sockfd,struct sockaddr*address,socklen_t*address_len);

// 獲取socketfd遠端的地址信息
int getpeername(int sockfd,struct sockaddr*address,socklen_t*address_len);

成功返回0,失敗返回-1設置errno

socketfd屬性設置,option

#include<sys/socket.h>
int getsockopt(int sockfd,int level,int option_name,
				void*option_value,socklen_t*restrict option_len);
int setsockopt(int sockfd,int level,int option_name,
				const void*option_value,socklen_t option_len);

成功返回0,失敗返回-1設置errno,記錄一下option_name,后面用到結(jié)合具體實例分析

gethostbyname & gethostbyaddr

根據(jù)主機名稱獲取主機的完整信息、根據(jù)地址獲取主機的完整信息,信息返回結(jié)構如下:

#include<netdb.h>
struct hostent
{
    char* h_name; /*主機名*/
    char** h_aliases; /*主機別名列表,可能有多個*/
    int h_addrtype; /*地址類型(地址族)*/
    int h_length; /*地址長度*/
    char** h_addr_list /*按網(wǎng)絡字節(jié)序列出的主機IP地址列表*/
};

getservbyname & getservbyport

根據(jù)服務名稱或端口號獲取服務信息,從/etc/services獲取信息,該文件中存放的是知名端口號和協(xié)議等信息。返回結(jié)構體如下:

#include<netdb.h>
struct servent
{
    char* s_name; /*服務名稱*/
    char** s_aliases; /*服務的別名列表,可能有多個*/
    int s_port; /*端口號*/
    char* s_proto; /*服務類型,通常是tcp或者udp*/
};

getaddrinfo

可以認為是調(diào)用了gethostbyname和getservbyname

#include<netdb.h>
// hostname:可以是主機名或IP地址字符串
// service:可以接收服務名,也可以接收十進制端口號
// result指向返回結(jié)果的鏈表,結(jié)構為addrinfo
int getaddrinfo(const char* hostname,const char* service,const
struct addrinfo* hints,struct addrinfo** result);

addrinfo結(jié)構體:

struct addrinfo
{
int ai_flags; /*大部分設置hints參數(shù)*/
int ai_family; /*地址族*/
int ai_socktype; /*服務類型,SOCK_STREAM或SOCK_DGRAM*/
int ai_protocol; /*通常設置為0*/
socklen_t ai_addrlen; /*socket地址ai_addr的長度*/
char* ai_canonname; /*主機的別名*/
struct sockaddr* ai_addr; /*指向socket地址*/
struct addrinfo* ai_next; /*指向下一個sockinfo結(jié)構的對象*/
};

getaddrinfo結(jié)束后,釋放result分配的堆內(nèi)存

void freeaddrinfo(struct addrinfo* res);

getnameinfo

可以認為是調(diào)用了gethostbyaddr和getservbyport

#include<netdb.h>
// 返回的主機名存儲在host,服務名存儲在serv
int getnameinfo(const struct sockaddr *sockaddr,socklen_t addrlen,
	char* host,socklen_t hostlen,char *serv,socklen_t servlen,int flags);
gai_strerror

轉(zhuǎn)換getnameinfo和getaddrinfo返回的錯誤碼為可讀的字符串

#include<netdb.h>
const char* gai_strerror(int error);

getaddrinfo和getnameinfo返回的錯誤碼如下:

簡單示例

testserver.cc,testserver 0.0.0.0 8889

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

#include <cassert>
#include <cstdio>
#include <iostream>
using namespace std;

int main(int argc, char *argv[]) {
  if (argc <= 2) {
    cout << "usage:" << argv[0] << " ip_address port_number" << endl;
    return 0;
  }

  const char *ip = argv[1];
  int port = atoi(argv[2]);
  struct sockaddr_in address, client_addr;
  address.sin_family = AF_INET;
  address.sin_port = htons(port);
  inet_pton(AF_INET, ip, &address.sin_addr);
  int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  int ret = bind(sockfd, (struct sockaddr *)&address, sizeof(address));
  assert(ret != -1);
  ret = listen(sockfd, 2);
  assert(ret != -1);
  socklen_t client_addr_length = sizeof(client_addr);
  int conn =
      accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_length);
  if (conn < 0)
    cout << "connect error: " << errno << endl;
  else {
    string hello = "hello client";
    send(conn, hello.data(), sizeof(hello), 0);
    close(conn);
  }
  close(sockfd);
  return 0;
}

testclient.cc,/etc/hosts加入server的地址和主機名,testclient myserver

#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

#include <cassert>
#include <iostream>

using namespace std;

int main(int argc, char* argv[]) {
  if (argc != 2) {
    cout << "usage: " << argv[0] << " hostname" << endl;
    return 0;
  }
  char* hostname = argv[1];
  // 獲取主機信息
  struct hostent* hostinfo = gethostbyname(hostname);
  assert(hostinfo);

  /*
    獲取server返回信息,自定義一個服務,
    編輯/etc/services, my	        8889/tcp
  */
  struct servent* servinfo = getservbyname("my", "tcp");
  assert(servinfo);
  cout << "myserver port is " << ntohs(servinfo->s_port) << endl;
  struct sockaddr_in address;
  address.sin_family = AF_INET;
  address.sin_port = servinfo->s_port;

  address.sin_addr = *(struct in_addr*)*hostinfo->h_addr_list;
  int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  int result = connect(sockfd, (struct sockaddr*)&address, sizeof(address));
  assert(result != -1);
  char buffer[128];
  result = recv(sockfd, buffer, sizeof(buffer), 0);
  cout << "resceived: " << result << endl;
  assert(result > 0);
  buffer[result] = '\0';
  cout << "server's message: " << buffer << endl;
  close(sockfd);
  return 0;
}

學習自:
《Linux高性能服務器編程》
《UNIX環(huán)境高級編程》
《UNIX系統(tǒng)編程》

總結(jié)

以上是生活随笔為你收集整理的Linux socket API的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。