Linux系统编程学习相关博文
Linux系统编程------网络编程的学习
- 一、概述
-
- [1. TCP/UDP](#1. TCP/UDP)
- [2. 端口号](#2. 端口号)
- [3. 字节序](#3. 字节序)
- [4. Sockt服务器和客户端的开发步骤](#4. Sockt服务器和客户端的开发步骤)
-
- [1. 服务器](#1. 服务器)
- [2. 客户端](#2. 客户端)
- 二、网络编程API
- 三、API介绍
-
- [1. socket函数](#1. socket函数)
- [2. bind函数](#2. bind函数)
- [3. listen函数](#3. listen函数)
- [4. accept函数](#4. accept函数)
- [5. connect函数](#5. connect函数)
- [6. inet_aton函数](#6. inet_aton函数)
- [7. inet_ntoa函数](#7. inet_ntoa函数)
- [8. htons函数](#8. htons函数)
- 四、API的使用例子
一、概述
1. TCP/UDP
- TCP面向连接( 如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
- TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
- TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低 (对实时应用很有用,如IP电话,实时视频会议等)
- 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
- TCP首部开销20字节;UDP的首部开销小,只有8个字节
- TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
2. 端口号
一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等。这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。实际上是通过"IP地址+端口号"来区分不同的服务的。端口提供了一种访问通道,服务器一般都是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP (简单文件传送协议) 服务器的UDP端口号都是69。
3. 字节序
- 小端字节序 (Little endian):将低序字节存储在起始地址
- 大端字节序 (Big endian):将高序字节存储在起始地址
- 网络字节序 = 大端字节序
4. Sockt服务器和客户端的开发步骤
1. 服务器
- 创建套接字
- 为套接字添加信息 (IP地址和端口号)
- 监听网络连接
- 监听到有客户端接入,接受一个连接
- 数据交互
- 关闭套接字,断开连接
2. 客户端
- 创建套接字
- 连接服务器 (输入服务器IP地址和端口号)
- 数据交互
- 关闭套接字,断开连接
二、网络编程API
在Linux系统中,操作系统提供了一系列的API,详细看下图
创建套接字 socket()
绑定 bind()
监听 listen()
连接(服务器) accept()
连接(客户端) connect()
地址转换 inet_aton() / inet_ntoa
字节序转换 htons() / htonl() / ntohs() / ntohl()
三、API介绍
1. socket函数
c
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
1. 函数功能:创建套接字
2. 形参说明:
domain:指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)
- AF_INET IPV4因特网域
- AF_INET6 IPv6因特网域
- AF_UNIX Unix域
- AF_ROUTE 路由套接字
- AF_KEY 密钥套接字
- AF_UNSPEC 未指定
type:指定 socket 的类型
- SOCK STREAM
流式套接字提供可靠的、面向连接的通信流,它使用 TCP 协议,从而保证了数据传输的正确性和顺序性
- SOCK DGRAM
数据报套接字定义了一种无连接的服,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议 UDP
- SOCK RAW
允许程序使用底层协议,原始套接字允许对底层协议如 IP 或 ICMP 进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发
protocal:通常赋值为0
- 0 选择 type 类型对应的默认协议
- IPPROTO_TCP TCP 传输协议
- IPPROTO_UDP UDP 传输协议
- IPPROTO_SCTP SCTP 传输协议
- IPPROTO_TIPC TIPC 传输协议
3. 返回值:成功,返回套接字的文件描述符;失败,返回-1
2. bind函数
c
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
1. 函数功能:用于绑定IP地址和端口号到sockfd
2. 形参说明:
sockfd:调用socket函数返回的套接字描述符
addr:是一个指向包含有本机 IP 地址及端口号等信息的 sockaddr 类型的指针,指向要绑定给 sockfd 的协议地址结构,这个地址结构根据地址创建 socket 时的地址协议族的不同而不同(该结构体原型在下方)
addrlen:addr的大小
struct sockaddr结构体原型:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
可同等替换为:
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* 协议族 */
__be16 sin_port; /* 端口号 */
struct in_addr sin_addr; /* IP地址结构体 */
/* 填充到结构体sockaddr的大小,没有实际意义,只是为跟sockaddr结构体在内存中对齐,这样两者才能相互转换 */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
3. 返回值:成功,返回0;失败,返回-1
3. listen函数
c
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
1. 函数功能:监听设置,设置能处理的最大连接数
- 设置能处理的最大连接数,listen() 并末开始接受连线,只是设置 sockect 的 listen 模式,listen 函数只用于服务端,服务路进程不知道要与谁连接,因此,它不会主动地要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。主要就两个功能:将一个未连接的套接字转换为一个被动套接字(监听),规定内核为相应套接字排队的最大连接数。
- 内核为任何一个给定监听套接字维护两个队列:
- 未完成连接队列,每个这样的 SYN 报文段对应其中一项:已由某个客户发出并到达服务醒,而服务器正在等待完成相应的 TCP 二次握手过程。这些套接字处于 SYN REVD 状态;
- 已完成连接队列,每个已完成 TCP 三次握手过程的客户端对应其中一项。这些套接字处于 ESTABLISHED 状态
2. 形参说明:
sockfd:调用socket函数返回的套接字描述符
backlog:请求队列允许的最大请求数
3. 返回值:成功,返回0;失败,返回-1
4. accept函数
c
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
1. 函数功能:用于从已完成连接队列队头返回下一个已完成的连接。如果已完成连接队列为空,那么进程被投入睡眠
2. 形参说明:
sockfd:调用socket函数返回的套接字描述符
addr:用来返回已连接的对端(客户端)的协议地址
addrlen:客户端地址长度
3. 返回值:该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在,内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示 TCP 三次挥手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。成功,返回0;失败,返回-1
5. connect函数
c
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
1. 函数功能:绑定之后的client端(客户端),与服务器建立连接
2. 形参说明:
sockfd:目的服务器的套接字描述符
addr:服务器端的IP地址和端口号的结构体指针
addrlen:addr大小
3. 返回值:成功,返回0;失败,返回-1
6. inet_aton函数
c
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);
1. 函数功能:将字符串形式的 IP 地址转为网络能识别的格式
2. 形参说明:
cp:IP地址
inp:存放IP地址的结构体指针(如下所示)
struct sockaddr结构体原型:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
可同等替换为:
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* 协议族 */
__be16 sin_port; /* 端口号 */
struct in_addr sin_addr; /* IP地址结构体 */
/* 填充到结构体sockaddr的大小,没有实际意义,只是为跟sockaddr结构体在内存中对齐,这样两者才能相互转换 */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
7. inet_ntoa函数
c
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);
1. 函数功能:将网络格式的IP地址转为字符串格式
2. 形参说明:
in:存放IP地址的结构体(结构体原型见上方)
8. htons函数
c
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);
1. 函数功能:返回网络字节序的值(关于什么是字节序请看概述部分),用来转换端口号
2. 形参说明:
hostshort:转换的端口号。h代表host,n代表net,s代表short(两个字节),l代表long(4个字节),通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取
四、API的使用例子
实现功能 :服务器可以接收各个客户端的信息,每隔五秒服务器向连接的客户端发送信息(类似心跳包)
1. 服务端
c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 #include <sys/types.h> /* See NOTES */
6 #include <sys/socket.h>
7 #include <arpa/inet.h>
8 #include <netinet/in.h>
9
10 //1. int socket(int domain, int type, int protocol);
11 //2. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
12 //3. uint16_t htons(uint16_t hostshort);
13 //4. int inet_aton(const char *cp, struct in_addr *inp);
14 //5. int listen(int sockfd, int backlog);
15 //6. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
16
17 int main(int argc, char **argv)
18 {
19 int mark = 0;
20 int c_fd = 0;
21 int s_fd = 0;
22 int s_bind = 0;
23 int c_lent = 0;
24 int n_read = 0;
25 int s_listen = 0;
26
27 char msg[128] = {'\0'};
28 char readBuf[128] = {'\0'};
29 char sendBuf[128] = {'\0'};
30 //char *sendBuf = "Thank you for your message";
31
32 struct sockaddr_in s_addr;
33 struct sockaddr_in c_addr;
34
35 memset(&s_addr, 0, sizeof(struct sockaddr_in));
36 memset(&c_addr, 0, sizeof(struct sockaddr_in));
37
38 if(argc != 3){
39 printf("Please input three params\n");
40 exit(-1);
41 }
42
43 //1. socket
44 s_fd = socket(AF_INET, SOCK_STREAM, 0);
45 if(s_fd == -1){
46 perror("Create socekt failed:");
47 exit(-1);
48 }
49
50 //2. bind
51 s_addr.sin_family = AF_INET;
52 s_addr.sin_port = htons(atoi(argv[2]));
53 inet_aton(argv[1], &s_addr.sin_addr);
54
55 s_bind = bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
56 if(s_bind == -1){
57 perror("Bind failed:");
58 exit(-1);
59 }
60
61 //3. listen
62 s_listen = listen(s_fd, 10);
63 if(s_listen == -1){
64 perror("Listen failed:");
65 exit(-1);
66 }
67
68 printf("listen...\n");
69
70 //4. accept
71 while(1){
72 c_lent = sizeof(struct sockaddr_in);
73 c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &c_lent);
74 if(c_fd == -1){
75 perror("Accept failed:");
76 exit(-1);
77 }
78
79 printf("Get connect: %s\n", inet_ntoa(c_addr.sin_addr));
80
81 mark++;
82
83 if(fork() == 0){
84 while(1){
85 memset(sendBuf, '\0', sizeof(sendBuf));
86
87 sprintf(sendBuf, "Welcome No.%d client", mark);
88 write(c_fd, sendBuf, sizeof(sendBuf));
89 sleep(5);
90
91 if(fork() == 0){
92 while(1){
93 memset(msg, '\0', sizeof(msg));
94 memset(readBuf, '\0', sizeof(readBuf));
95
96 n_read = read(c_fd, readBuf, sizeof(readBuf));
97 if(n_read == -1){
98 printf("Read failed:");
99 }else{
100 sprintf(msg, "Get No.%d message: %s", mark, readBuf);
101 printf("%s\n", msg);
102 }
103 }
104 }
105 }
106 }
107 }
108
109 return 0;
110 }
2. 客户端
c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 #include <sys/types.h> /* See NOTES */
6 #include <sys/socket.h>
7 #include <arpa/inet.h>
8 #include <netinet/in.h>
9
10 //1. int socket(int domain, int type, int protocol);
11 //2. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
12 //3. int inet_aton(const char *cp, struct in_addr *inp);
13 //4. char *inet_ntoa(struct in_addr in);
14
15 int main(int argc, char **argv)
16 {
17 int c_fd = 0;
18 int n_read = 0;
19
20 char readBuf[128] = {'\0'};
21 char sendBuf[128] = {'\0'};
22 //char *sendBuf = "Message from client";
23
24 struct sockaddr_in c_addr;
25
26 memset(&c_addr, 0, sizeof(struct sockaddr_in));
27
28 if(argc != 3){
29 printf("Please input three params\n");
30 exit(-1);
31 }
32
33 //1. socket
34 c_fd = socket(AF_INET, SOCK_STREAM, 0);
35 if(c_fd == -1){
36 perror("Create socekt failed:");
37 exit(-1);
38 }
39
40 //2. connect
41 c_addr.sin_family = AF_INET;
42 c_addr.sin_port = htons(atoi(argv[2]));
43 inet_aton(argv[1], &c_addr.sin_addr);
44
45 if(connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr_in)) == -1){
46 perror("Connect failed:");
47 exit(-1);
48 }
49
50 while(1){
51 memset(sendBuf, '\0', sizeof(sendBuf));
52
53 printf("input :");
54 gets(sendBuf);
55 write(c_fd, sendBuf, strlen(sendBuf));
56
57 if(fork() == 0){
58 while(1){
59 memset(readBuf, '\0', sizeof(readBuf));
60
61 n_read = read(c_fd, readBuf, sizeof(readBuf));
62 if(n_read == -1){
63 printf("Read failed:");
64 }else{
65 printf("Get server message: %s\n", readBuf);
66 }
67 }
68 }
69 }
70
71 return 0;
72 }