Socket中接口函數簡介


在網絡編程中客戶端和服務端所需要的函數如下圖:
在這裏插入圖片描述
在這裏插入圖片描述

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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章