文章目錄
在網絡編程中客戶端和服務端所需要的函數如下圖:
1.創建socket
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
這個函數建立一個協議族爲domain、協議類型爲type、協議編號爲protocol的套接字文件描述符。如果函數調用成功,會返回一個標識這個套接字的文件描述符,失敗的時候返回-1。
domain
函數socket()的參數domain用於設置網絡通信的域,函數socket()根據這個參數選擇通信協議的族。通信協議族在文件sys/socket.h中定義。
如下,domain的值及含義
type
函數socket()的參數type用於設置套接字通信的類型,主要有SOCKET_STREAM(流式套接字)、SOCK——DGRAM(數據包套接字)等。
如下type的值及含義
並不是所有的協議族都實現了這些協議類型,例如,AF_INET協議族就沒有實現SOCK_SEQPACKET協議類型。
protocol
函數socket()的第3個參數protocol用於制定某個協議的特定類型,即type類型中的某個類型。通常某協議中只有一種特定類型,這樣protocol參數僅能設置爲0;但是有些協議有多種特定的類型,就需要設置這個參數來選擇特定的類型。
類型爲SOCK_STREAM的套接字表示一個雙向的字節流,與管道類似。流式的套接字在進行數據收發之前必須已經連接,連接使用connect()函數進行。一旦連接,可以使用read()或者write()函數進行數據的傳輸。流式通信方式保證數據不會丟失或者重複接收,當數據在一段時間內任然沒有接受完畢,可以將這個連接人爲已經死掉。
SOCK_DGRAM和SOCK_RAW 這個兩種套接字可以使用函數sendto()來發送數據,使用recvfrom()函數接受數據,recvfrom()接受來自制定IP地址的發送方的數據。
SOCK_PACKET是一種專用的數據包,它直接從設備驅動接受數據。
errno
函數socket()並不總是執行成功,有可能會出現錯誤,錯誤的產生有多種原因,可以通過errno獲得:
如下 errno的值及含義
示例,建立一個流式套接字:
int sock = socket(AF_INET, SOCK_STREAM, 0);
2.命名socket
socket() 函數用來創建套接字,確定套接字的各種屬性,然後服務器端要用 bind() 函數將套接字與特定的IP地址和端口綁定起來,只有這樣,流經該IP地址和端口的數據才能交給套接字處理;而客戶端要用 connect() 函數建立連接。
sockaddr_in
這個結構體用來處理網絡通信的地址。
bind
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
-sock:文件描述符
-addr:sockaddr結構體變量的指針
-addrlen:addr變量的大小,可用sizeof()計算
返回值:
代碼演示:
將創建的套接字與IP地址127.0.0.1、端口8888綁定。
//創建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, 0);
//創建sockaddr_in結構體變量
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每個字節都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具體的IP地址
serv_addr.sin_port = htons(8888); //端口
//將套接字和IP、端口綁定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
-inet_addr 將字符串形式的IP地址 -> 網絡字節順序 的整型值
-inet_ntoa 網絡字節順序的整型值 ->字符串形式的IP地址
-網絡字節順序與本地字節順序之間的轉換函數:
htonl()–“Host to Network Long”
ntohl()–“Network to Host Long”
htons()–“Host to Network Short”
ntohs()–“Network to Host Short”
connect()
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);
3.監聽socket
socket被命名之後還不能馬上接受客戶的連接,需要創建一個監聽隊列存放待處理的客戶連接。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
-sockfd:指定被監聽的socket
-backlog:提示內核監聽隊列的最大長度。監聽隊列的長度如果超過backlog,服務器將不受理新的客戶端連接,客戶端也將收到ECONNREFUSED錯誤信息。
返回值:成功返回0,失敗返回-1
請求隊列:當套接字正在處理客戶端請求時,如果有新的請求進來,套接字是沒法處理的,只能把它放進緩衝區,待當前請求處理完畢後,再從緩衝區中讀取出來處理。如果不斷有新的請求進來,它們就按照先後順序在緩衝區中排隊,直到緩衝區滿。這個緩衝區,就稱爲請求隊列(Request Queue)。
4.接收連接
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *addr, socklen *addrlen);
-sockfd:執行過listen系統調用的監聽socket
-addr:獲取被接受連接的遠端socket地址
-返回值:失敗返回-1
accpet()成功時返回一個新的連接socket,該socket唯一的表示了被接受的這個連接,服務器可通過讀寫該socket來與被接受連接對應的客戶端通信。
5.發起連接(暫時瞭解)
客戶端通過connect調用主動的與服務器建立連接。
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);
6.關閉連接
close()
#include <unistd.h>
int close(int fd);
-fd:待關閉的socket的描述符
將fd的引用計數減一隻有當引用計數爲0的時候才真正的關閉連接。一次fock將是的父進程當中打開的socket引用計數加一。因此必須都關閉纔會真正的將連接關閉。
showdown()
調用立即終止連接。
7.TCP數據讀寫
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd,void *buf,size_t len,int flags);
ssize_t send(int sockfd,const void *buf,size_t len,int flags);
8.UDP數據讀寫(瞭解)
9.實現多線程TCP服務器
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<string.h>
#include<pthread.h>
void* thread_start(void* arg){
int client = *(int*)arg;
//保存接收到的數據
char buf[1024] = { 0 };
for (;;){
int recvlen = recv(client, buf, sizeof(buf)-1, 0);
if (recvlen <= 0)break;
buf[recvlen] = '\0';
if (strstr(buf, "quit") != NULL){
char re[] = "quit success!\n";
send(client, re, strlen(re) + 1, 0);
break;
}
int sendlen = send(client, "ok\n", 4, 0);
printf("recv %s\n", buf);
}
close(client);
}
int main(int argc, char* argv[]){
//創建socket
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1){
printf("create socket failed!\n");
return -1;
}
//綁定一個端口
unsigned short port = 9999;
if (argc > 1){
port = atoi(argv[1]);//將字符串轉換爲一個整數
}
sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);//將主機字節順序轉換爲網絡字節順序【默認大端】(處理大端小端問題)
saddr.sin_addr.s_addr = htonl(0);
if (bind(sock, (sockaddr*)&saddr, sizeof(saddr)) != 0){
printf("bind port %d failed!\n", saddr.sin_port);
return -2;
}
printf("bind port %d success!\n", port);
listen(sock, 10);//在一段時間內最大可以讓多少個鏈接進來
for (;;){
//接受客戶端請求
sockaddr_in caddr;
socklen_t len = sizeof(caddr);
//int client=accept(sock, 0, 0);
int client = accept(sock, (sockaddr*)&caddr, &len);
if (client <= 0)break;
printf("accept client %d \n", client);
char* ip = inet_ntoa(caddr.sin_addr);
unsigned short cport = ntohs(caddr.sin_port);
printf("client ip is %s,port is %d\n", ip, cport);
//創建線程
pthread_t tid;
pthread_create(&tid, NULL, thread_start, (void*)&client);
//將主線程與子線程分離,子線程執行完畢後自動釋放資源
pthread_detach(tid);
}
close(sock);
return 0;
}