0%

网络编程

TCP/IP的具体含义

从字面意义上讲,有人可能会认为TCP/IP是指TCP与IP两种协议。实际生活当中有时也确实就是指这两种协议。然而在很多情况下,它只是利用IP进行通信时所必须用到的协议群的统称。具体来说,IP或ICMP、TCP或UDP、TELNET或FTP、以及HTTP等都属于TCP/IP的协议。它们与TCP或IP的关系紧密,是互联网必不可少的组成部分。TCP/IP一词泛指这些协议,因此,有时也称TCP/IP为网际协议族(网际协议族(Internet Protocol Suite):组成网际协议的一组协议) 。

image-20211210095906196

image-20211210100043783

image-20211210100129045

IP(IPv4、IPv6)相当于OSI参考模型中的第3层——网络层。网络层的主要作用是“实现终端节点之间的通信”。这种终端节点之间的通信也叫“点对点(end-to-end)通信”。

IP地址的定义

IP地址(IPv4地址)由32位正整数来表示。TCP/IP通信要求将这样的IP地址分配给每一个参与通信的主机。IP地址在计算机内部以二进制(二进制是指用0、1表示数字的方法。) 方式被处理。然而,由于人类社会并不习惯于采用二进制方式,需要采用一种特殊的标记方式。那就是将32位的IP地址以每8位为一组,分成4组,每组以“.”隔开,再将每组数转换为十进制数(这种方法也叫做“十进制点符号”(Dot-decimalnotation)。) 。下面举例说明这一方法。

image-20211210100449379

image-20211210100739729

IP地址的分类

IP地址分为四个级别,分别为A类、B类、C类、D类(还有一个一直未使用的E类。) 。它根据IP地址中从第1位到第4位的比特列对其网络标识和主机标识进行区分。

image-20211210101056676

image-20211210101444952

TCP/UDP

TCP/IP中有两个具有代表性的传输层协议,它们分别是TCP和UDP。TCP提供可靠的通信传输,而UDP则常被用于让广播和细节控制交给应用的通信传输。总之,根据通信的具体特征,选择合适的传输层协议是非常重要的。

TCP/IP的众多应用协议大多以客户端/服务端的形式运行。客户端(客户端(Client)具有客户的意思。在计算机网络中是提供服务和使用服务的一方。) 类似于客户的意思,是请求的发起端。而服务端(服务端(Server)在计算机网络中则意味着提供服务的程序或计算机。) 则表示提供服务的意思,是请求的处理端。另外,作为服务端的程序有必要提前启动,准备接收客户端的请求。否则即使有客户端的请求发过来,也无法做到相应的处理。

image-20211210102650606

TCP

TCP是面向连接的、可靠的流协议。流就是指不间断的数据结构,你可以把它想象成排水管道中的水流。当应用程序采用TCP发送消息时,虽然可以保证发送的顺序,但还是犹如没有任何间隔的数据流发送给接收端(例如,在发送端应用程序发送了10次100字节的消息,那么在接收端,应用程序有可能会收到一个1000字节连续不间断的数据。因此在TCP通信中,发送端应用可以在自己所要发送的消息中设置一个表示长度或间隔的字段信息。) 。

TCP为提供可靠性传输,实行“顺序控制”或“重发控制”机制。此外还具备“流控制(流量控制)”、“拥塞控制”、提高网络利用率等众多功能。

UDP

UDP是不具有可靠性的数据报协议。细微的处理它会交给上层的应用去完成。在UDP的情况下,虽然可以确保发送消息的大小(例如,发送端应用程序发送一个100字节的消息,那么接收端应用程序也会以100字节为长度接收数据。UDP中,消息长度的数据也会发送到接收端,因此在发送的消息中不需要设置一个表示
消息长度或间隔的字段信息。然而,UDP不具备可靠传输。所以,发送端发出去的消息在网络传输途中一旦丢失,接收端将收不到这个消息。) ,却不能保证消息一定会到达。因此,应用有时会根据自己的需要进行重发处理。

端口号

数据链路和IP中的地址,分别指的是MAC地址和IP地址。前者用来识别同一链路中不同的计算机,后者用来识别TCP/IP网络中互连的主机和路由器。在传输层中也有这种类似于地址的概念,那就是端口号。端口号用来识别同一台计算机中进行通信的不同应用程序。因此,它也被称为程序地址。

image-20211210103241167

TCP/IP或UDP/IP通信中通常采用5个信息来识别(这个信息可以在Unix或Windows系统中通过netstat -n 命令显示。) 一个通信。它们是“源IP地址”、“目标IP地址”、“协议号”、“源端口号”、“目标端口号”。只要其中某一项不同,则被认为是其他通信。

image-20211210103457828

网络编程主要函数

1.socket函数

1
int socket(int domain, int type,int protocol);

此函数用于创建一个套接字。
domain是网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等)。AF_UNIX只能够用于单一的Unix 系统进程间通信,而AF_INET是针对Internet的,因而可以允许远程通信使用。
type是网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM等)。
SOCK_STREAM表明用的是TCP 协议,这样会提供按顺序的,可靠,双向,面向连接的比特流。
SOCK_DGRAM 表明用的是UDP协议,这样只会提不可靠,无连接的通信。
关于protocol,由于指定了type,所以这个地方一般只要用0来代替就可以了。
此函数执行成功时返回文件描述符,失败时返回-1,看errno可知道出错的详细情况。

2.bind函数

1
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

从函数用于将地址绑定到一个套接字。
sockfd是由socket函数调用返回的文件描述符。
my_addr是一个指向sockaddr的指针。
addrlen是sockaddr结构的长度。

sockaddr的定义:

1
2
3
4
struct sockaddr{
unisgned short as_family;
char sa_data[14];
};

不过由于系统的兼容性,我们一般使用另外一个结构(struct sockaddr_in) 来代替。

1
2
3
4
5
6
struct sockaddr_in{
unsigned short sin_family; //AF_INET
unsigned short sin_port; //端口号
struct in_addr sin_addr; //IP地址 设置为INADDR_ANY表示可以和任何的主机通信
unsigned char sin_zero[8]; //0
}

3.listen函数

1
int listen(int sockfd,int backlog);

此函数宣告服务器可以接受连接请求。
sockfd是bind后的文件描述符。
backlog设置请求排队的最大长度。当有多个客户端程序和服务端相连时,使用这个表示可以介绍的排队长度。
listen函数将bind的文件描述符变为监听套接字,返回的情况和bind一样

4.accept函数

1
int accept(int sockfd, struct sockaddr *addr,int *addrlen);

服务器使用此函数获得连接请求,并且建立连接。
sockfd是listen后的文件描述符。
addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了, bind,listen和accept是服务器端用的函数。
accept调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接。 accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了,失败时返回-1 。

5.connect函数

1
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen);

可以用connect建立一个连接,在connect中所指定的地址是想与之通信的服务器的地址。
sockfd是socket函数返回的文件描述符。
serv_addr储存了服务器端的连接信息,其中sin_add是服务端的地址。
addrlen是serv_addr的长度
connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符,失败时返回-1。

6.send函数

1
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

sockfd 指定发送端套接字描述符;
buf 指明一个存放应用程序要发送数据的缓冲区;
len 指明实际要发送的数据的字节数;
flags 一般置0。
客户或者服务器应用程序都用send函数来向TCP连接的另一端发送数据

7.recv函数

1
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

sockfd 指定接收端套接字描述符;
buf 指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
len 指明buf的长度;
flags 一般置0。
客户或者服务器应用程序都用recv函数从TCP连接的另一端接收数据。

8.recvfrom函数

1
2
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);

recvfrom通常用于无连接套接字,因为此函数可以获得发送者的地址。
src_addr 是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。
addrlen 常置为sizeof (struct sockaddr)

9.sendto函数

1
2
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);

sendto和send相似,区别在于sendto允许在无连接的套接字上指定一个目标地址。
dest_addr 表示目地机的IP地址和端口号信息,
addrlen 常常被赋值为sizeof (struct sockaddr)。
sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。

TCP编程

image-20211210114343966

三次握手

image-20211210114948643

四次挥手

image-20211210115002656

server

socket——bind——listen—-accept—send、recv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>


/* socket
* bind
* listen
* accept
* send/recv
*/

#define SERVER_PORT 8888
#define BACKLOG 10

int main(int argc, char **argv)
{
int iSocketServer;
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
struct sockaddr_in tSocketClientAddr;
int iRet;
int iAddrLen;

int iRecvLen;
unsigned char ucRecvBuf[1000];

int iClientNum = -1;

signal(SIGCHLD,SIG_IGN);

iSocketServer = socket(AF_INET, SOCK_STREAM, 0); //创建套接字
if (-1 == iSocketServer)
{
printf("socket error!\n");
return -1;
}

tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short *///设置端口号
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
memset(tSocketServerAddr.sin_zero, 0, 8);

iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)); //绑定套接字
if (-1 == iRet)
{
printf("bind error!\n");
return -1;
}

iRet = listen(iSocketServer, BACKLOG);//开始监听
if (-1 == iRet)
{
printf("listen error!\n");
return -1;
}

while (1)
{
iAddrLen = sizeof(struct sockaddr);
iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (-1 != iSocketClient)
{
iClientNum++;
printf("Get connect from client %d : %s\n", iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));
if (!fork())
{
/* 子进程源码*/
while (1)
{
/* 接收客户端的数据并显示出来 */
iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
if (iRecvLen <= 0)
{
close(iSocketClient);
return -1;
}
else
{
ucRecvBuf[iRecvLen] = '\0';
printf("Get Msg From Client %d: %s\n", iClientNum, ucRecvBuf);
}
}
}
}
}

close(iSocketServer);
return 0;
}



client

socket—–connect——send/recv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>

/* socket
* connect
* send/recv
*/

#define SERVER_PORT 8888

int main(int argc, char **argv)
{
int iSocketClient;
struct sockaddr_in tSocketServerAddr;

int iRet;
unsigned char ucSendBuf[1000];
int iSendLen;

if (argc != 2)
{
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}

iSocketClient = socket(AF_INET, SOCK_STREAM, 0);

tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
printf("invalid server_ip\n");
return -1;
}
memset(tSocketServerAddr.sin_zero, 0, 8);


iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("connect error!\n");
return -1;
}

while (1)
{
if (fgets(ucSendBuf, 999, stdin))
{
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
if (iSendLen <= 0)
{
close(iSocketClient);
return -1;
}
}
}

return 0;
}


实验结果

客户端1

1
2
3
4
5
6
book@100ask:~/01_all_series_quickstart/04_嵌入式Linux应用开发基础知识/source/12_socket/tcp$ ./client 127.0.0.1
123456
111111
aaaaaa


客户端2

1
2
3
4
5
book@100ask:~/01_all_series_quickstart/04_嵌入式Linux应用开发基础知识/source/12_socket/tcp$ ./client 192.168.88.130
qazwsxedc
aaaaaaaaa
ssdfff

服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
book@100ask:~/01_all_series_quickstart/04_嵌入式Linux应用开发基础知识/source/12_socket/tcp$ ./server
Get connect from client 0 : 127.0.0.1
Get Msg From Client 0: 123456

Get Msg From Client 0: 111111

Get Msg From Client 0: aaaaaa

Get connect from client 1 : 192.168.88.130
Get Msg From Client 1: qazwsxedc

Get Msg From Client 1: aaaaaaaaa

Get Msg From Client 1: ssdfff

udp编程

image-20211210155425799

server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>


/* socket
* bind
* sendto/recvfrom
*/

#define SERVER_PORT 8888

int main(int argc, char **argv)
{
int iSocketServer;
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
struct sockaddr_in tSocketClientAddr;
int iRet;
int iAddrLen;

int iRecvLen;
unsigned char ucRecvBuf[1000];

int iClientNum = -1;

iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == iSocketServer)
{
printf("socket error!\n");
return -1;
}

tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
memset(tSocketServerAddr.sin_zero, 0, 8);

iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("bind error!\n");
return -1;
}


while (1)
{
iAddrLen = sizeof(struct sockaddr);
iRecvLen = recvfrom(iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (iRecvLen > 0)
{
ucRecvBuf[iRecvLen] = '\0';
printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
}
}

close(iSocketServer);
return 0;
}

client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>

/* socket
* connect
* send/recv
*/

#define SERVER_PORT 8888

int main(int argc, char **argv)
{
int iSocketClient;
struct sockaddr_in tSocketServerAddr;

int iRet;
unsigned char ucSendBuf[1000];
int iSendLen;

if (argc != 2)
{
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}

iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);

tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
printf("invalid server_ip\n");
return -1;
}
memset(tSocketServerAddr.sin_zero, 0, 8);


iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("connect error!\n");
return -1;
}

while (1)
{
if (fgets(ucSendBuf, 999, stdin))
{
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
if (iSendLen <= 0)
{
close(iSocketClient);
return -1;
}
}
}

return 0;
}