Linux下socket程式設計基礎1-初探
Linux下socket套接字基礎
int socket(int __domain, int __type, int __protocol);
- domain:是協議域,這個引數決定了socket的地址型別,常用的是AF_INET(ipv4地址)和16位埠號組合
- type:是指定socket型別,常用的socket型別,SOCK_STREAM(基於TCP),SOCK_DGRAM(基於UDP)
- protocol: 指定協議,常用IPPROTO_TCP、IPPTOTO_UDP等
呼叫socket()返回的描述字存在於協議族空間中,但是沒有一個具體的地址,所以需要呼叫下面的bind()函式設定具體地址,否則當呼叫connect()、listen()時這個埠系統會隨機分配。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd:就是socket的描述字,socket()的返回值
- *addr :
一個指向sockaddr結構體的指標,指向要繫結給sockfd的協議地址,這個結構體隨著地址結構的改變而呼叫不同的結構體
- 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 */ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ };
- ipv6:
struct sockaddr_in6 { sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port; /* port number */ uint32_t sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */ }; struct in6_addr { unsigned char s6_addr[16]; /* IPv6 address */ };
- addlen: 對應的是地址的長度
int listen(int sockfd, int backlog);
服務端通過listen()將socket轉變為被動型別,等待客戶的連線請求
- sockfd: 第一個引數即為要監聽的socket描述字.
- backlog: 第二個引數為相應socket可以排隊的最大連線個數。socket()函式建立的socket預設是一個主動型別的,listen函式將socket變為被動型別的,等待客戶的連線請求。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
客戶端通過呼叫connect()來建立於伺服器的連線
- sockfd: 第一個引數是客戶端的socket描述字
- *addr : 第二個引數是伺服器的socket地址
- addrlen :第三個引數是伺服器socket地址的長度
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
TCP伺服器端依次呼叫socket()、bind()、listen()之後,就會監聽指定的socket地址了。TCP客戶端依次呼叫socket()、connect()之後就想TCP伺服器傳送了一個連線請求。TCP伺服器監聽到這個請求之後,就會呼叫accept()函式取接收請求,這樣連線就建立好了。之後就可以開始網路I/O操作了,即類同於普通檔案的讀寫I/O操作。
- sockfd : 伺服器socket描述字
- *addr : 返回客戶端的socket地址
- *addrlen : 客戶端的socket地址長度
- 返回值: 已連線的socket描述字
注意:伺服器通常只需要建立一個socket套接字,這個套接字在伺服器生命週期內一直存在。而客戶端連線成功服務端後,核心會建立一個新的已連線的socket套接字,當伺服器對此客戶端完成服務之後,這個socket描述字就會被關閉。
recvmsg()和sendmsg()函式(最通用的I/O函式)
用這兩個函式可替代
- read()/write()
- recv()/send()
- readv()/writev()
- recvfrom()/sendto()
#include <sys/socket.h>
#include <sys/uio.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
struct msghdr {
void *msg_name; // protocol address
socklen_t msg_namelen; // size of protocol address
struct iovec *msg_iov; // scatter/gather array
int msg_iovlen; // elements in msg_iov
void *msg_control; // ancillary data (cmsghdr struct)
socklen_t msg_controllen; // length of ancillary data
int msg_flags; // flags returned by recvmsg()
};
struct iovec {//依賴於標頭檔案sys/uio.h
void *iov_base; /* starting address of buffer */
size_t iov_len; /* size of buffer */
}
-
msg_name和msg_namelen
這兩個成員用於套接字未連線的場合(如未連線 UDP 套接字)。
如果用於TCP的話,msg_name置為空指標,namelen置為0。 -
msg_iov和msg_iovlen
這兩個成員指定輸入輸出緩衝區陣列以及陣列的多少,結合struct iovec來看,msg_iov是個二維陣列 -
msg_control和msg_controllen
這兩個成員指定可選的輔助資料的位置和大小 -
msg_flags
對於sendmsg,這個引數可以忽略掉。
對於recvmsg,它本身的引數flags會被複制到msg_flags成員,由核心使用這個值驅動接收處理過程。
recvmsg 返回的 7 個標誌如下:
- MSG_BCAST:本標誌隨 BSD/OS 引入,相對較新。它的返回條件是本資料包作為鏈路層廣播收取或者其目的 IP 地址是一個廣播地址。與 IP_RECVD-STADDR 套接字選項相比,本標誌是用於判定一個 UPD 資料包是否發往某個廣播地址的更好方法。
- MSG_MCAST:本標誌隨 BSD/OS 引入,相對較新。它的返回條件是本資料報作為鏈路層多播收取。
- MSG_TRUNC:本標誌的返回條件是本資料報被截斷,也就是說,核心預備返回的資料超過程序事先分配的空間(所有 iov_len 成員之和)。
- MSG_CTRUNC:本標誌的返回條件是本資料報的輔助資料被截斷,也就是說,核心預備返回的輔助資料超過程序事先分配的空間(msg_controllen)。
- MSG_EOR:本標誌的返回條件是返回資料結束一個邏輯記錄。TCP 不使用本標誌,因為它是一個位元組流協議。
- MSG_OOB:本標誌絕不為 TCP 帶外資料返回。它用於其他協議族(如 OSI 協議族)。
- MSG_NOTIFICATION:本標誌由 SCTP 接收者返回,指示讀入的訊息是一個事先通知,而不是資料訊息。
示例程式碼
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <netinet/in.h>
using namespace std;
void My_server(void){
//初始化傳送資料緩衝區
char buf[128]={0};
struct iovec buffer={
.iov_base = buf,
.iov_len = sizeof(buf)
};
struct msghdr Mymsg={
.msg_name = nullptr,
.msg_namelen =0,
.msg_iov = &buffer,
.msg_iovlen = 1,
.msg_control = nullptr,
.msg_controllen = 0,
.msg_flags = 0
};
//建立套接字
int Mysocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
//將套接字和IP、埠繫結
struct sockaddr_in Server_Addr;
memset(&Server_Addr,0,sizeof(Server_Addr));
Server_Addr.sin_family=AF_INET; //設定使用ipv4地址
Server_Addr.sin_addr.s_addr=inet_addr("127.0.0.1");//設定具體的ip地址(本地環回)
Server_Addr.sin_port=htons(12345); //埠
bind(Mysocket,(struct sockaddr *)&Server_Addr,sizeof(Server_Addr));//應用 地址和埠的設定
//進入監聽狀態
listen(Mysocket,20);
//接收客戶端請求
struct sockaddr_in Client_Addr;
socklen_t client_addr_size = sizeof(Client_Addr);
std::cout << "ready accept"<<endl;
int client_sock = accept(Mysocket,(struct sockaddr *)&Client_Addr,&client_addr_size);//在此阻塞,等待客戶端請求
std::cout << "received message"<<endl;
//向客戶端傳送資料
char str[] = "hello,world!";
Mymsg.msg_iov->iov_base = str;
sendmsg(client_sock,&Mymsg,0); //write(client_sock,str,sizeof(str));用write也是可以的
close(client_sock);
close(Mysocket);
}
void My_client(void){
char buf[128] = {0};
struct iovec buffer={
.iov_base = buf,
.iov_len = sizeof(buf)
};
struct msghdr Mymsg={
.msg_name = nullptr,
.msg_namelen = 0,
.msg_iov = &buffer,
.msg_iovlen = 1,
.msg_control = nullptr,
.msg_controllen = 0,
.msg_flags = 0
};
int Mysocket = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in Server_Addr;
memset(&Server_Addr,0,sizeof(Server_Addr));
Server_Addr.sin_family=AF_INET;
Server_Addr.sin_addr.s_addr=inet_addr("127.0.0.1");
Server_Addr.sin_port=htons(12345);
connect(Mysocket,(struct sockaddr*)&Server_Addr,sizeof(Server_Addr));
std::cout << "ready accept message"<<endl;
recvmsg(Mysocket,&Mymsg,0); //接收資訊
printf("%s\n",Mymsg.msg_iov->iov_base);
std::cout << "read message done"<<endl;
close(Mysocket);
}
int main(int argc, char **argv){
string select;
_Start:
std::cout << "Select Mode Server or Client:(c/s)";
cin >> select;
std::cout << "select:" << select <<endl;
if(!select.compare("s")){
My_server();
}else if(!select.compare("c")){
My_client();
}else{
std::cout << "please input \"server\" or \"client\""<<endl;
std::cout << "Please re - enter parameters at boot time"<<endl;
goto _Start;
}
return 0;
}
/*
建立套接字: socket()
繫結本機埠: bind()
建立連線: connect(),accept()
偵聽埠: listen()
資料傳輸: send(), recv()
輸入/輸出多路複用: select()
關閉套接字: closesocket()
*/
Makefile太麻煩了,利用cmake編譯
/***CMakeLists.txt***/
cmake_minimum_required(VERSION 3.10.2)
PROJECT(mysocket)
add_executable(Mysocket Mysocket.cpp)