網絡編程之-套接字

一、ip地址和端口號
1.1 ip地址
ip地址有兩個版本:ipv4和ipv6,這裏介紹的是ipv4:

  1. ip地址是在ip協議中,用來標識網絡中不同主機的地址,它分爲兩個部分:網絡號:路由時依據網絡號。 主機號:找具體的主機。
  2. 對於ipv4來說,ip地址是一個4字節,32位的整數。
  3. 人們通常用點分十進制的字符串來表示ip地址,比如127.0.0.1 (每一個用點分隔的數字表示一字節)

1.2 端口號
可以認爲,網絡通信的本質也是進程間通信,所以雖然ip地址能夠把數據發送到對端機器上,但還需要有一個標識符來區分出這個數據要給那個進程來解析,而這個標識符就是端口號。

  1. 端口號是一個2字節,16位的整數,是傳輸層協議的內容

  2. 用來標識一個進程,告訴內核要把數據交給那個進程

  3. ip地址+端口號能標識 網絡中唯一一臺主機上唯一一個進程

  4. 一個端口號只能被一個進程佔據。

    這裏需要注意的一點是,進程id也唯一標識了一個唯一的進程,要把端口號和進程Id區分清楚:進程應用於網絡纔有端口號。
    舉個例子:我們打10086相當於就是ip地址,而轉人工客服小姐姐就相當於端口號。

二、傳輸層的兩個協議
這兩個協議在後面的文章會詳細介紹,這裏呢先簡單說一下。
UDP協議:無連接、不可靠、面向數據報。
TCP協議:有連接、可靠、面向字節流。

這裏需要注意面向數據報和麪向字節流的區別:
UDP協議是面向數據報的,也就是說讀的時候只能整包整包的讀數據,不能讀半包,一包半的。
而TCP協議是面向字節流的,想讀多少就能讀多少。

通過這些特點,我們可以看出來TCP在功能上是優於UDP的,但UDP還有個顯著的特點,就是簡單。

三、網絡字節序
內存中多字節數據在內存中的存儲有大小端之分:網路數據流也有大小端之分。
爲了統一,TCP/IP規定,網絡數據流採用大端,最高有效位存於最低內存地址處,最低有效位存於最高內存處。
具體細節就是,如果發送端是小端,就需要先把數據轉爲大端在發送;如果發送端是大端,直接發送。

實際代碼編寫中,可以調用下面的庫函數進行網絡字節序和主機字節序的轉換:
在這裏插入圖片描述
函數名是見名知意的,拿第一個舉例:
在這裏插入圖片描述
我們編寫套接字時,常使用htonl函數將端口號的主機序列轉爲網絡字節序列。

四、常見的socket接口
這些函數具體的介紹,有一個大神寫得很好,附上鍊接:https://blog.csdn.net/y396397735/article/details/50655363

五、sockaddr結構
sockaddr和void*類似,是爲了泛型處理不同的套接,適用於各種底層不同地址格式的網絡協議,比如:ipv4,ipv6等。
在這裏插入圖片描述
如圖,不同的網絡協議的底層不同;
對於ipv4,地址類型爲AF_INET,對於ipv6,地址類型爲AF_INET6,只要取得sockaddr結構體的頭部16字節的地址類型,就可以根據地址類型確定結構體的內容了。
在使用socket API的時候,需要強轉爲sockaddr,這能增強程序的通用性。
在這裏插入圖片描述
在這裏插入圖片描述
基於ipv4編程,使用的是sockaddr_in,這個結構體主要包含三部分:地址類型、端口號、ip地址。

地址轉換函數
在這裏插入圖片描述
六、UDP網絡通信

基於上面的知識,接下我們來實現一個簡單的UDP網絡通信。

  1. 對於udp,socket的參數使用的是SOCK_DGRAM;
  2. udp不進行連接,直接使用sendto和recvfrom來收發數據.

服務器:

#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>

int main(int argc, char* argv[]) 
{
	//採用./server [ip][port]的形式傳入ip地址和端口號
	if(argc != 3)
	{
		printf("./server [ip] [port]\n");
		return 1;
	}
	// ipv4  udp
	int sock = socket(AF_INET, SOCK_DGRAM, 0);
	if(sock < 0)
	{
		printf("socket error\n");
		return 2;
	}
	//構造出地址協議-ip地址+端口號
	struct sockaddr_in local;
	local.sin_family = AF_INET;
	local.sin_addr.s_addr = inet_addr(argv[1]);
	local.sin_port = htons(atoi(argv[2]));
	//綁定端口號
	if( bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
	{
		printf("bind error\n");
		return 3;
	}
	while(1)
	{
		char buf[1024];
		struct sockaddr_in client;
		socklen_t len = sizeof(client);
		//udp不建立連接,直接對數據進行讀寫
		ssize_t s = recvfrom(sock, buf, sizeof(buf)-1, 0, (struct sockaddr*)&client, &len);
		if(s > 0)
		{
			buf[s] = 0;
			printf("client>:%s\n", buf);
			sendto(sock, buf, strlen(buf), 0, (struct sockaddr*)&client, sizeof(client));
		}
	}
	return 0;
}

客戶端:

#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>

int main(int argc, char* argv[])
{
	if(argc != 3)
	{
		printf("./client [ip] [port]\n");
		return 1;
	}
	int sock = socket(AF_INET,SOCK_DGRAM, 0);
	if(sock < 0)
	{
		printf("socket error\n");
		return 2;
	}
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr(argv[1]);
	addr.sin_port = htons(atoi(argv[2]));
	//udp無需建立連接,直接進行數據傳輸
	while(1)
	{	
		char buf[1024];
		struct sockaddr_in peer;
		socklen_t len = sizeof(peer);
		ssize_t s = read(0, buf, sizeof(buf-1));
		if(s > 0)
		{	
			sendto(sock, buf, strlen(buf), 0, (struct sockaddr*)&addr, sizeof(addr));
			ssize_t s2 = recvfrom(sock, buf, sizeof(buf)-1, 0, (struct sockaddr*)&peer, &len);
			if(s2 > 0);
			{
				buf[s2] = 0;
				printf("#>:%s\n", buf);
			}
		}
	}	
	return 0;
}

來看一下運行的效果:
在這裏插入圖片描述
在這裏插入圖片描述
服務器收到來自客戶端的數據,輸出並且回顯給客戶端。

七、TCP網絡通信
服務器:

int main(int argc,char* argv[])
{
    if(argc != 3){
        printf("./server [ip] [port]\n");
        return 1;
    }

    int listen_sock = socket(AF_INET,SOCK_STREAM,0);
    if(listen_sock < 0){
        printf("socket error\n");
        return 2;
    }

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = inet_addr(argv[1]);
    local.sin_port = htons(atoi(argv[2]));

    if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local)) < 0){
        printf("bind error\n");
        return 3;
    }

    if(listen(listen_sock,5) < 0){
        printf("listen error\n");
        return 4;
    }

    while(1){
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);
        if(new_sock < 0){
            perror("accept");
            return 5;
        }

        while(1){
            char buf[1024] = {0};
            ssize_t s = read(new_sock,buf,sizeof(buf));
            if(s < 0){
                perror("read");
                break;
            }
            else if(s == 0){
                printf("客戶端已退出,可以關閉該連接");
                close(new_sock);
                break;
            }
            else{
                printf("client: %s\n",buf);
            }
        }
    }

    return 0;
}

客戶端:

int main(int argc,char* argv[])
{
    if(argc != 3){
        printf("./client [ip] [port]\n");
        return 1;
    }

    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0){
        printf("socket error\n");
        return 2;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    addr.sin_port = htons(atoi(argv[2]));

    if(connect(sock,(struct sockaddr*)&addr,sizeof(addr)) < 0){
        printf("connect error\n");
        return 3;
    }

    while(1){
        char buf[1024] = {0};
        ssize_t s = read(0,buf,sizeof(buf));
        if(s > 0){
            ssize_t s2 = write(sock,buf,strlen(buf));
        }
    }

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