Linux系统编程——网络编程的学习

Linux系统编程学习相关博文

  1. Linux系统编程——文件编程的学习
  2. Linux系统编程——进程的学习
  3. Linux系统编程——进程间通信的学习
  4. Linux系统编程——线程的学习

一、概述

1. TCP/UDP

  1. TCP面向连接( 如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
  2. TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
  3. TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低 (对实时应用很有用,如IP电话,实时视频会议等)
  4. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
  5. TCP首部开销20字节;UDP的首部开销小,只有8个字节
  6. TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

2. 端口号

一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等。这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区分不同的服务的。端口提供了一种访问通道,服务器一般都是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP (简单文件传送协议) 服务器的UDP端口号都是69。

3. 字节序

  1. 小端字节序 (Little endian):将低序字节存储在起始地址
  2. 大端字节序 (Big endian):将高序字节存储在起始地址
  3. 网络字节序 = 大端字节序

4. Sockt服务器和客户端的开发步骤

1. 服务器

  1. 创建套接字
  2. 为套接字添加信息 (IP地址和端口号)
  3. 监听网络连接
  4. 监听到有客户端接入,接受一个连接
  5. 数据交互
  6. 关闭套接字,断开连接

2. 客户端

  1. 创建套接字
  2. 连接服务器 (输入服务器IP地址和端口号)
  3. 数据交互
  4. 关闭套接字,断开连接
    在这里插入图片描述

二、网络编程API

在Linux系统中,操作系统提供了一系列的API,详细看下图

创建套接字	socket()
绑定		bind()
监听		listen()
连接(服务器)		accept()
连接(客户端)		connect()

地址转换		inet_aton() / inet_ntoa
字节序转换	htons() / htonl() / ntohs() / ntohl()

三、API介绍

1. socket函数

#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函数

#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函数

#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函数

#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函数

#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函数

#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函数

#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函数

#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. 服务端

  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. 客户端

  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 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值