1 1. 网络基础概念
2 1.1 协议
3 概念:协议事先约定好,大家共同遵守的一组规则,如交通信号灯.
4 从应用程序的角度看,协议可理解为数据传输和数据解释的规则;
5 可以简单的理解为各个主机之间进行通信所使用的共同语言.
6
7 假设,A、B双方欲传输文件。规定:
8 第一次:传输文件名,接收方接收到文件名,应答OK给传输方;
9 第二次:发送文件的尺寸,接收方接收到该数据再次应答一个OK;
10 第三次:传输文件内容。同样,接收方接收数据完成后应答OK表示文件内容接收成功。
11
12 1.2 OSI七层模型
13 OSl是Open System lnterconnection的缩写,意为开放式系统互联,国际标准化组织(ISO)制定了OSI模型,
14 该模型定义了不同计算机互联的标准,是设计和描述计算机网络通信的基本框架
15
16 网络分层OS17层模型:物数网传会表应
17 物理层:
18 双绞线,光纤(传输介质),将模拟信号转换为数字信号
19 数据链路层:(ARP RARP)
20 [数据校验],定义了网络传输的基本单位"帧".
21 网络层:(IP,、ICMP协议、IGMP协议)
22 定义网络,两台机器之间传输的路径选择"点到点"的传输(路由器)
23 传输层:(TCP UDP)
24 传输数据TCP,UDP,"端到端"的传输
25 会话层:
26 通过传输层建立数据传输的通道.
27 表示层:
28 编解码,翻译工作。
29 应用层:(http ssh email ftp)
30 为客户提供各种应用服务,email服务,ftp服务,ssh服务
31
32
33 TCP四层模型:
34 数据链路层:物理层+数据链路程
35 网络层:网络层
36 传输层:传输层
37 应用层:会话层,表示层,应用层
38
39
40 1.3 以太网帧格式
41 以太网帧格式就是包装在网络接口层(数据链路层)的协议
42
43 目的地址 源地址 类型 数据 CRC
44 6 6 2 46~1500 4 (字节单位)
45
46 类型:
47 0800:IP数据报(46~1500)
48 0806:ARP请求/应答(28) + PAD(18)
49 8035: RARP请求/应答(28) + PAD(18)
50
51 以ARP协议为例介绍以太网帧格式:
52
53
54
序号:TCP是安全可靠的,每个数据包都带有序号,当数据包丢失的时候,需要重传,要使用序号进行重传控制数据有序,
55 丢包重传.
56 确认序号:使用确认序号可以知道对方是否已经收到了,通过确认序号可以知道哪个序号的数据需要重传.
57 16位窗口大小一滑动窗口(主要进行流量控制)
58
59
60
61 2. socket编程预备知识
62
63 网络字节序:
64 大端和小端的概念
65 大端: 低位地址存放高位数据, 高位地址存放低位数据
66 小端: 低位地址存放低位数据, 高位地址存放高位数据
67
68 大端和小端的使用使用场合???
69 大端和小端只是对数据类型长度是两个及以上的, 如int short, 对于单字节没限制,在网络中经常需要考虑大端和小
70 端的是IP和端口.
71
72
73
74 3. 一些函数
75 网络传输用的是大端法, 如果机器用的是小端法, 则需要进行大小端的转换.
76 下面4个函数就是进行大小端转换的函数:
77 #include <arpa/inet.h>
78
79 uint32_t htonl(uint32_t hostlong);
80 uint16_t htons(uint16_t hostshort);
81 uint32_t ntohl(uint32_t netlong);
82 uint16_t ntohs(uint16_t netshort);
83 函数名的h表示主机host, n表示网络network, s表示short, l表示long
84 上述的几个函数, 如果本来不需要转换函数内部就不会做转换.
85
86 IP地址转换函数:
87 p->表示点分十进制的字符串形式
88 to->到
89 n->表示network网络
90
91 int inet_pton(int af, const char *src, void *dst);
92 函数说明: 将字符串形式的点分十进制IP转换为大端模式的网络IP(整形4字节数)
93 参数说明:
94 af: AF_INET
95 src: 字符串形式的点分十进制的IP地址
96 dst: 存放转换后的变量的地址
97 例如: inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
98
99 手工也可以计算: 如192.168.232.145, 先将4个正数分别转换为16进制数,
100 192--->0xC0 168--->0xA8 232--->0xE8 145--->0x91
101 最后按照大端字节序存放: 0x91E8A8C0, 这个就是4字节的整形值.
102
103
104 const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
105 函数说明: 网络IP转换为字符串形式的点分十进制的IP
106 参数说明:
107 af: AF_INET
108 src: 网络的整形的IP地址
109 dst: 转换后的IP地址,一般为字符串数组
110 size: dst的长度
111 返回值:
112 成功--返回指向dst的指针
113 失败--返回NULL, 并设置errno
114
115 例如: IP地址为010aa8c0, 转换为点分十进制的格式:
116 01---->1 0a---->10 a8---->168 c0---->192
117 由于从网络中的IP地址是高端模式, 所以转换为点分十进制后应该为: 192.168.10.1
118
119
120
121 struct sockaddr ==> struct sockaddr_in
122
123 struct sockaddr {
124 sa_family_t sa_family;
125 char sa_data[14];
126 }
127
128 struct sockaddr_in {
129 sa_family_t sin_family; /* address family: AF_INET */
130 in_port_t sin_port; /* port in network byte order */
131 struct in_addr sin_addr; /* internet address */
132 };
133
134 /* Internet address. */
135 struct in_addr {
136 uint32_t s_addr; /* address in network byte order */
137 }; //网络字节序IP--大端模式
138
139
140 通过man 7 ip可以查看相关说明
141
142
143 4. socket编程主要的API函数介绍
144
145 int socket(int domain, int type, int protocol);
146 函数描述: 创建socket
147 参数说明:
148 domain: 协议版本
149 AF_INET: IPV4
150 AF_INET6: IPV6
151 AF_UNIX AF_LOCAL本地套接字使用
152 type:协议类型
153 SOCK_STREAM 流式, 默认使用的协议是TCP协议
154 SOCK_DGRAM 报式, 默认使用的是UDP协议
155 protocal:
156 一般填0, 表示使用对应类型的默认协议.
157 返回值:
158 成功: 返回一个大于0的文件描述符
159 失败: 返回-1, 并设置errno
160
161 当调用socket函数以后, 返回一个文件描述符, 内核会提供与该文件描述符相对应的读和写缓冲区,
同时还有两个队列, 分别是请求连接队列和已连接队列.
162
163
164 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
165 函数描述: 将socket文件描述符和IP,PORT绑定
166 参数说明:
167 socket: 调用socket函数返回的文件描述符
168 addr: 本地服务器的IP地址和PORT,
169 struct sockaddr_in serv;
170 serv.sin_family = AF_INET;
171 serv.sin_port = htons(8888);
172 //serv.sin_addr.s_addr = htonl(INADDR_ANY);
173 //INADDR_ANY: 表示使用本机任意有效的可用IP
174
175 inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
176 addrlen: addr变量的占用的内存大小
177 返回值:
178 成功: 返回0
179 失败: 返回-1, 并设置errno
180
181
182 int listen(int sockfd, int backlog);
183 函数描述: 将套接字由主动态变为被动态
184 参数说明:
185 sockfd: 调用socket函数返回的文件描述符
186 backlog: 同时请求连接的最大个数(还未建立连接)
187 返回值:
188 成功: 返回0
189 失败: 返回-1, 并设置errno
190
191 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
192 函数说明:获得一个连接, 若当前没有连接则会阻塞等待.
193 函数参数:
194 sockfd: 调用socket函数返回的文件描述符
195 addr: 传出参数, 保存客户端的地址信息
196 addrlen: 传入传出参数, addr变量所占内存空间大小
197 返回值:
198 成功: 返回一个新的文件描述符,用于和客户端通信
199 失败: 返回-1, 并设置errno值.
200
201 accept函数是一个阻塞函数, 若没有新的连接请求, 则一直阻塞.
202 从已连接队列中获取一个新的连接, 并获得一个新的文件描述符, 该文件描述符用于和客户端通信.
(内核会负责将请求队列中的连接拿到已连接队列中)
203
204 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
205 函数说明: 连接服务器
206 函数参数:
207 sockfd: 调用socket函数返回的文件描述符
208 addr: 服务端的地址信息
209 addrlen: addr变量的内存大小
210 返回值:
211 成功: 返回0
212 失败: 返回-1, 并设置errno值
213
214
215 //读写操作
216 接下来就可以使用write和read函数进行读写操作了.
217 除了使用read/write函数以外, 还可以使用recv和send函数
218
219 读取数据和发送数据:
220 ssize_t read(int fd, void *buf, size_t count);
221 ssize_t write(int fd, const void *buf, size_t count);
222
223 ssize_t recv(int sockfd, void *buf, size_t len, int flags);
224 ssize_t send(int sockfd, const void *buf, size_t len, int flags);
225 对应recv和send这两个函数flags直接填0就可以了.
226
227 注意: 如果写缓冲区已满, write也会阻塞, read读操作的时候, 若读缓冲区没有数据会引起阻塞.
228
229
230 服务端开发流程:
231 1.创建socket,返回一个文件描述符lfd---socket()
232 --该文件描述符用于监听客户端连接
233 2.将lfd和IP PORT进行绑定----bind()
234 3.将lfd由主动变为被动监听----listen()
235 4.接受一个新的连接,得到一个文件描述符cfd----accept()
236 ---该文件描述符是用于和客户端进行通信的
237 5.while(1)
238 {
239 接收数据---read或者recv
240 发送数据---write或者send
241 }
242 6.关闭文件描述符----close(1fd)close(cfd);
代码client.c
1 //这里是客户端的代码
2 //功能:主要是先从STDIN_FILENO里面去读取数据,然后将数据发送给服务端;
3 // 服务端会先去读取信息,将读取到的信息进行全部转换成大写,然后再发回送给客户端
4
5
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <unistd.h>
9 #include <sys/socket.h>
10 #include <sys/types.h>
11 #include <string.h>
12 #include <arpa/inet.h>
13
14 //客户端的代码
15
16 int main ()
17 {
18 int ret;
19
20 //创建直接通信文件描述符
21 int sockfd = socket(AF_INET, SOCK_STREAM, 0);
22 if(sockfd < 0)
23 {
24 perror("sockfd error\n");
25 return -1;
26 }
27
28
29 //连接服务器
30 struct sockaddr_in server_add; //服务端的信息
31
32 server_add.sin_family = AF_INET;
33 server_add.sin_port = htons(8080);
34 inet_pton(AF_INET, "127.0.0.1", &server_add.sin_addr.s_addr);
35
36 ret = connect(sockfd, (struct sockaddr *) &(server_add), sizeof(server_add));
37 if(ret < 0)
38 {
39 perror("connect error\n");
40 return -1;
41 }
42
43 //发送和读消息
44 int n = 0;
45 char buf[1024];
46
47 while(1)
48 {
49 //读取标准输入数据
50 memset(buf, 0, sizeof(buf));
51 n = read(STDIN_FILENO, buf, sizeof(buf));
52
53 //发送数据
54 write(sockfd, buf, n);
55
56 //读服务端发来的数据
57 memset(buf, 0, sizeof(buf));
58 n = read(sockfd, buf, sizeof(buf));
59 if(n <= 0)
60 {
61 printf("read error | server exit,n = [%d]\n",n);
62 break;
63 }
64
65 printf("n == [%d], buf == [%s]\n",n ,buf);
66 }
67
68 close(sockfd);
69
70 return 0;
71 }
72
73
代码server.c
1 //这里是客户端的代码
2 //功能:主要是先从STDIN_FILENO里面去读取数据,然后将数据发送给服务端;
3 // 服务端会先去读取信息,将读取到的信息进行全部转换成大写,然后再发回送给客户端
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <unistd.h>
8 #include <sys/socket.h>
9 #include <sys/types.h>
10 #include <string.h>
11 #include <arpa/inet.h>
12
13 int main ()
14 {
15 int ret;
16
17 //创建监听文件描述符
18 int sockfd = socket(AF_INET, SOCK_STREAM, 0);
19 if(sockfd < 0)
20 {
21 perror("sockfd error\n");
22 return -1;
23 }
24
25 //绑定ip 以及 端口port
26 struct sockaddr_in server_add; //服务器端的结构体,包含服务器端的ip和port
27 bzero(&server_add, sizeof(server_add));
28
29 server_add.sin_family = AF_INET;
30 server_add.sin_port = htons(8080);
31 inet_pton(AF_INET, "127.0.0.1", &server_add.sin_addr.s_addr);
32
33 ret = bind(sockfd, (struct sockaddr *)&server_add, sizeof(server_add));
34 if(ret < 0)
35 {
36 perror("bind error\n");
37 return -1;
38 }
39
40 //listen由主动变成被动态
41 ret = listen(sockfd, 10);
42 if(ret < 0)
43 {
44 perror("listen error\n");
45 return -1;
46 }
47
48
49 //接受一个新的连接,得到一个可通信的文件描述符
50 struct sockaddr_in client_addr; //用来接收客户端的信息的
51 int len = sizeof(client_addr);
52 int newsockfd = accept(sockfd,(struct sockaddr*) &(client_addr), &len);
53 if(newsockfd < 0)
54 {
55 perror("accept error\n");
56 return -1;
57 }
58
59 printf("sockfd = [%d], newsockfd = [%d]\n",sockfd, newsockfd);
60
61 //打印出接收的客户端的信息
62 char client_ip[16];
63 printf("client: ip = %s, port = %d\n", inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip,
sizeof(client_ip)), ntohs(client_addr.sin_port));
64
65 //接收消息
66 int i, n;
67 char buf[1024];
68 while(1)
69 {
70 memset(buf, 0, sizeof(buf));
71
72 //服务端先去接收数据
73 n = recv(newsockfd, buf, sizeof(buf),0);
74 if(n <= 0)
75 {
76 printf("recv error | client exit, n = [%d]\n",n);
77 break;
78 }
79
80 //打印接收到的数据字节数和数据内容printf("server: n = [%d], buf = [%s]\n",n, recv_buf);
81 printf("server: n = [%d], buf = [%s]\n",n, buf);
82 for(i=0; i<n; i++)
83 {
84 buf[i] = toupper(buf[i]);//将小写转换成大写
85 }
86
87 //发送数据出去到客户端
88 send(newsockfd, buf, n, 0);
89 }
90
91 close(sockfd);
92 close(newsockfd);
93
94 }
95
96