一、ip地址和端口號
1.1 ip地址
ip地址有兩個版本:ipv4和ipv6,這裏介紹的是ipv4:
- ip地址是在ip協議中,用來標識網絡中不同主機的地址,它分爲兩個部分:網絡號:路由時依據網絡號。 主機號:找具體的主機。
- 對於ipv4來說,ip地址是一個4字節,32位的整數。
- 人們通常用點分十進制的字符串來表示ip地址,比如127.0.0.1 (每一個用點分隔的數字表示一字節)
1.2 端口號
可以認爲,網絡通信的本質也是進程間通信,所以雖然ip地址能夠把數據發送到對端機器上,但還需要有一個標識符來區分出這個數據要給那個進程來解析,而這個標識符就是端口號。
-
端口號是一個2字節,16位的整數,是傳輸層協議的內容
-
用來標識一個進程,告訴內核要把數據交給那個進程
-
ip地址+端口號能標識 網絡中 的 唯一一臺主機上 的 唯一一個進程。
-
一個端口號只能被一個進程佔據。
這裏需要注意的一點是,進程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網絡通信。
- 對於udp,socket的參數使用的是SOCK_DGRAM;
- 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;
}