Linux系统之网络编程

Linux系统之网络编程

1 网络基础理论

1.1 OSI模型与TCP/IP协议

OSI体系结构,意为开放式系统互联。
国际标准组织(国际标准化组织)制定了OSI模型。
OSI模型把网络通信分为7层,分别是物理层数据链路层网络层传输层会话层表示层应用层
OSI体系模型

1.2 TCP/IP体系模型

在这里插入图片描述

1.3 UDP与TCP

1.3.1 UDP的优缺点

1. 数据不可靠,会丢包,不会出现粘包和拆包现象

1.3.2 TCP的优缺点

1. 面向连接,数据可靠,不丢包,但是会出现粘包和拆包问题
2. 粘包:分多次发送小数据,接收方的buf中会存放一次的数据
3. 拆包:接收包接收大文件时,分好几次进行接收大文件或者是大数据

1.4 IP、端口和字节序

1.4.1 IP: 用来标识主机

IP地址的分类

IP地址常用用途
A类地址分配给规模特别大的网络使用
B类地址分配给一般的中型网络
C类地址分配给小型网络,如一般的局域网和校园网
D类地址一般多用于广播
E类地址保留地址

1.4.2 端口: 标识是哪个进程

特殊的端口号

端口号端口说明
22SSH远程连接
5900VNC远程连接
80/443/8080常见的web服务端口
53DNS域名系统

1.4.3 字节序:

1. 小端(little - endian)—— 低序字节存储在低地址
2. 大端(big - endian)—— 高序地址存储在低地址
3. 网络字节序:都是用的大端存储
4. 在使用网络传输数据的时候,需要注意字节序(大小端模式)

2 编程搭建服务器和客户端

2.1 socket接口相关函数

2.1.1 socket()函数

#include <sys/types.h>        
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
/**************************************************** 
* Description:  创建一个socket套接字
* Input:         domain:地址描述
* 						AF_INET  指的是IPV4
* 						AF_INET6 指的是IPV6
*				 type: 套接字的类型
*						SOCK_STREAM   指的是TCP连接
*						SOCK_DGRAM	  指的是UDP连接
*				 protocol: 套接字所用的协议  一般填0
* Return:        成功:返回新的套接字
* 				 失败:返回 -1
* Others:        
*****************************************************/

2.1.2 bind()函数

#include <sys/types.h>        
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
/**************************************************** 
* Description:  绑定指定的IP地址
* Input:         sockfd:socket套接字
*				 my_addr: 绑定套接字的地址,这是一个协议族,使用的时候需要强转,才不会出现警告
*						struct sockaddr_in addr;
*						addr.sin_family = AF_INET;
*						addr.sin_port = htons(PORT);
*						addr.sin_addr.s_addr = inet_addr(IP);
*				 addrlen: 绑定地址的长度,sizeof(addr)
* Return:        成功:返回 0
* 				 失败:返回 -1
* Others:        
*****************************************************/

2.1.3 listen()函数

#include <sys/types.h>        
#include <sys/socket.h>

int listen(int s, int backlog);
/**************************************************** 
* Description:  自动接收到来的连接并且为连接队列指定一个长度限制
* Input:         s:socket套接字
*				 backlog: 已经完成连接正等待应用程序接收的套接字队列的长度
* Return:        成功:返回 0
* 				 失败:返回 -1
* Others:        
*****************************************************/

2.1.4 accept()函数

#include <sys/types.h>        
#include <sys/socket.h>

int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
/**************************************************** 
* Description:  自动接收到来的连接并且为连接队列指定一个长度限制
* Input:         s:socket套接字
*				 addr: 是已经连接的IP地址的结构体,使用的是同一个结构体
*						struct sockaddr_in addr;
*						但是这里也是需要强转的
*				 addrlen: 用于存放结构体的长度
* Return:        成功:返回 连接套接字connetfd
* 				 失败:返回 -1
* Others:        
*****************************************************/

2.1.5 connect()函数

#include <sys/types.h>        
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/**************************************************** 
* Description:  自动接收到来的连接并且为连接队列指定一个长度限制
* Input:         sockfd:socket套接字
*				 addr: 想要连接的IP地址的结构体
*						struct sockaddr_in addr;
*						addr.sin_family = AF_INET;
*						addr.sin_port = htons(PORT);
*						addr.sin_addr.s_addr = inet_addr(IP);
*				 addrlen: 用于存放结构体的长度
* Return:        成功:返回 0
* 				 失败:返回 -1
* Others:        
*****************************************************/

2.1.6 send()函数

#include <sys/types.h>        
#include <sys/socket.h>

int send(int s, const void *msg, size_t len, int flags);
/**************************************************** 
* Description:  TCP协议——发送数据包
* Input:         s:connet_fd套接字
*				 msg: 发送的buf包
*				 len: buf包的字节数
*				 flags:
*						一般设置为0  (0是阻塞的方式)
*						MSG_DONTWAIT  使用非阻塞方式发送
* Return:        成功:返回 0
* 				 失败:返回 -1
* Others:        
*****************************************************/

2.1.7 recv()函数

#include <sys/types.h>        
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
/**************************************************** 
* Description:  TCP协议——接收数据
* Input:         sockfd:connet_fd套接字(已经连接好的sock套接字,用变量connect_fd表示)
*				 buf: 接收buf缓冲区
*				 len: 接收缓冲区的字节数
*				 flags:
*						一般设置为0  (0是阻塞的方式)
*						MSG_DONTWAIT  使用非阻塞方式发送
* Return:        成功:返回 接收到的字节数
* 				 失败:返回 -1
* Others:        
*****************************************************/

2.1.8 sendto()函数

#include <sys/types.h>        
#include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
/**************************************************** 
* Description:  UDP协议——指定IP地址发送数据
* Input:         sockfd:connet_fd套接字(已经连接好的sock套接字,用变量connect_fd表示)
*				 buf: 接收buf缓冲区
*				 len: 接收缓冲区的字节数
*				 flags:
*						一般设置为0  (0是阻塞的方式)
*						MSG_DONTWAIT  使用非阻塞方式发送
*				 dest_addr: 向谁发送数据 传入接收方的IP地址
*						struct sockaddr_in server_addr;
*						server_addr.sin_family = AF_INET;
*						server_addr.sin_port = htons(PORT);
*						server_addr.sin_addr.s_addr = inet_addr(IP);
*						传参时,需要加强转
*				 addrlen:
*						传参的结构体的长度
*						sizeof(server_addr);
* Return:        成功:返回 发送数据的总字节数
* 				 失败:返回 -1
* Others:        
*****************************************************/

2.1.9 recvfrom()函数

#include <sys/types.h>        
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
/**************************************************** 
* Description:  UDP协议——接收数据
* Input:         sockfd:connet_fd套接字(已经连接好的sock套接字,用变量connect_fd表示)
*				 buf: 接收buf缓冲区
*				 len: 接收缓冲区的字节数
*				 flags:
*						一般设置为0  (0是阻塞的方式)
*						MSG_DONTWAIT  使用非阻塞方式发送
*				 dest_addr: 来自哪里的数据  传入发送方的IP地址
*						struct sockaddr_in server_addr;
*						server_addr.sin_family = AF_INET;
*						server_addr.sin_port = htons(PORT);
*						server_addr.sin_addr.s_addr = inet_addr(IP);
*						传参时,需要加强转
*				 addrlen:
*						传参的结构体的长度
*						sizeof(server_addr);
* Return:        成功:返回 发送数据的总字节数
* 				 失败:返回 -1
* Others:        
*****************************************************/

2.1.10 inet_aton()函数

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int inet_aton(const char *cp, struct in_addr *inp);
/**************************************************** 
* Description:  将一个字符串IP地址转换为一个32位的网络序列IP地址
* Input:         cp:要转换的IP地址字符串
*				 inp: 转换完成后,存放到in_addr结构体中
* Return:        成功:返回 非0
* 				 失败:返回 0
* Others:        
*****************************************************/

2.1.11 inet_ntoa()函数

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

char *inet_ntoa(struct in_addr in);
/**************************************************** 
* Description:  将一个32位的网络序列IP地址转换为一个字符串IP地址
* Input:         in:要转换的IP地址
* Return:        成功:返回 转换成功的字符串IP地址
* 				 失败:返回 NULL
* Others:        
*****************************************************/

2.1.12 inet_addr()函数

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

in_addr_t inet_addr(const char *cp);
/**************************************************** 
* Description:  将字符串的IP地址转换为一个32位的网络序列IP地址
* Input:         cp:要转换的字符串IP地址
* Return:        成功:返回 32位的网络序列IP地址
* 				 失败:返回 -1
* Others:        
*****************************************************/

2.1.13 htons()函数

#include <arpa/inet.h>

uint16_t htons(uint16_t hostshort);
/**************************************************** 
* Description:  将数字端口号转换为网络字节序端口号
* Input:         hostshort:数字端口号
* Return:        成功:网络字节序端口号
* 				 失败:
* Others:        
*****************************************************/

2.1.14 ntohs()函数

#include <arpa/inet.h>

uint16_t ntohs(uint16_t hostshort);
/**************************************************** 
* Description:  将网络字节序的端口号转换为数字端口号
* Input:         hostshort:要转换的网络字节序的端口号
* Return:        成功:数字端口号
* 				 失败:
* Others:        
*****************************************************/

2.2 TCP服务器和TCP客户端

2.2.1 使用socket接口搭建——TCP服务器

基本流程: socket-->bind-->listen-->accept-->recv/send-->close
/******************************
    > File Name    : TCP_Server.c
    > Author       : GK
    > Description  : 搭建TCP服务器
    > Created Time : 2020年11月03日 星期二 10时21分49秒
 *****************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <time.h>

int main(int argc, char *argv[])
{
	int connet_fd = 0;
	int socket_fd = 0;
	int re = 0;

	socket_fd = socket(AF_INET, SOCK_STREAM, 0);

	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(5555);
	addr.sin_addr.s_addr = inet_addr("192.168.1.192");

	re = bind(socket_fd, (struct sockaddr *)&addr, sizeof(addr)); // 绑定
	if (re == -1)
	{
		perror("bind");
		exit(-1);
	}
	
	listen(socket_fd, 255); //(开机)一直处于监听状态
	while (1)
	{
		connet_fd = accept(socket_fd, NULL, NULL); //	接听 accept函数只是将第一个客户端取出来,进行发数据,然后进入while(1)死循环中
												  
		printf("连接成功 connet_fd:%d\n", connet_fd);
		
		char send_buf[512];
		char recv_buf[512];
		while (1)
		{
			memset(send_buf, 0, sizeof(send_buf));
			memset(recv_buf, 0, sizeof(recv_buf));

			re = recv(connet_fd, recv_buf, sizeof(recv_buf), 0); //	接收客户端发送过来的消息
			if (re <= 0)
			{
				printf("客户端退出, re = %d\n", re);
				break;
			}

			if ((recv_buf[0] == 'G') && (recv_buf[1] == 'e') && (recv_buf[2] == 't'))
			{
				time_t seconds;
				time(&seconds);
				strcpy(send_buf, ctime(&seconds));
				
				printf("向客户端发送数据: %s\n", send_buf);
				
				re = send(connet_fd, send_buf, sizeof(send_buf), 0); //发送
				if (re == -1)
				{
					perror("send");
					break;
				}
			}
		}
		close(connet_fd); // 关闭链接通道
	}
	close(socket_fd); // 关机

	return 0;
}

2.2.2 使用socket接口搭建——TCP客户端

基本流程: socket-->bind(可选)-->connect-->send/recv-->close
/******************************
    > File Name    : TCP_Client.c
    > Author       : GK
    > Description  : 建立TCP客户端
    > Created Time : 2020年11月03日 星期二 10时20分42秒
 *****************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{	
	int socket_fd = 0;
	int re = 0;   

    socket_fd = socket(AF_INET, SOCK_STREAM, 0); //创建套接字(TCP)
	
    struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(5555);
	addr.sin_addr.s_addr = inet_addr("192.168.1.192");//填写服务器的IP地址

	re = connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr)); //连接服务器
    if(re == -1)
    {
        perror("connect");
        exit(-1);
    }
    printf("链接服务器成功\n");

	char send_buf[512];
	char recv_buf[512];
    while(1)
    {
        memset(send_buf, 0, sizeof(send_buf));
        memset(recv_buf, 0, sizeof(recv_buf));

        printf("请输入Get: ");
        scanf("%s", send_buf);
        if(*send_buf == 'q')
            break;
        else
            send(socket_fd, send_buf, sizeof(send_buf), 0);


        re = recv(socket_fd, recv_buf, sizeof(recv_buf), 0); //	接收客户端发送过来的消息
		if (re <= 0)
            break;
        else
        {
            if(recv_buf[0] == '#')
                printf("服务器下发的时间:%s\n", recv_buf+1);
        }
        
    }

    close(socket_fd);
	return 0;
}

2.3 UDP服务器和UDP客户端

2.3.1 使用socket接口搭建——UDP服务器

基本流程:socket-->bind-->recvfrom-->sendto-->close
/******************************
    > File Name    : UDP_Server.c
    > Author       : GK
    > Description  : 搭建UDP服务器
    > Created Time : 2020年11月03日 星期二 10时21分49秒
 *****************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>


int main(int argc, char *argv[])
{
	int socket_fd = 0;
	int re = 0;

	if(argc != 2)
	{
		printf("请输入IP地址\n");
		exit(-1);
	}
	socket_fd = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP socket

	//服务器自己的IP
	struct sockaddr_in server_addr, client_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(7777);
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);

	re = bind(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); // 绑定IP
	if (re == -1)
	{
		perror("bind");
		exit(-1);
	}
	else
	{
		puts("连接成功!\n");
	}
	
	int client_addr_len = sizeof(client_addr);
	
	char recv_buf[512];

	while(1)
	{
		memset(recv_buf, 0, sizeof(recv_buf));

		re = recvfrom(socket_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&client_addr, &client_addr_len);
		printf("re: %d bytes, recv_buf: %s\n", re, recv_buf);
		
		sendto(socket_fd, recv_buf, re, 0, (struct sockaddr *)&client_addr, sizeof(client_addr));
	}
	
	close(socket_fd);

	return 0;
}

2.3.2 使用socket接口搭建——UDP客户端

基本流程:socket-->bind(可选)-->recvfrom-->sendto-->close
/******************************
    > File Name    : UDP_Client.c
    > Author       : GK
    > Description  : 建立UDP客户端
    > Created Time : 2020年11月03日 星期二 10时20分42秒
 *****************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
	int socket_fd = 0;
    int addr_len = 0;
	int len = 0;
    
    if(argc != 2)
    {
        printf("请输入IP\n");
        exit(-1);
    }
	socket_fd = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP socket

	//服务器自己的IP
	struct sockaddr_in server_addr, client_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(7777);
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);  //填 "0" 代表本机

	addr_len = sizeof(server_addr);
	
	char send_buf[512];
	char recv_buf[512];
	while(1)
	{
		memset(send_buf, 0, sizeof(send_buf));
        memset(recv_buf, 0, sizeof(recv_buf));
        
        fgets(send_buf, sizeof(send_buf), stdin);
        send_buf[strlen(send_buf) - 1] = '\0';
        if(*send_buf == 'q')
            break;

        sendto(socket_fd, send_buf, strlen(send_buf), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));

		len = recvfrom(socket_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&server_addr, &addr_len);
		printf("re: %d bytes, recv_buf: %s\n", len, recv_buf);
		printf("IP: %s, Port: %s\n", inet_ntoa(server_addr.sin_addr, ntohs(server_addr.sin_port)));
	}
	
	close(socket_fd);

	return 0;
}

3 UDP广播与多播

3.1 广播与组播相关函数

3.1.1 setsockopt()函数

#include <sys/types.h>       
#include <sys/socket.h>

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
/**************************************************** 
* Description:  用于任意类型、任意状态套接口的设置选项值
* Input:         sockfd:socket套接字
* 				 level: 选项定义的层次
				 optname:需设置的选项
				 optval: 指向存放选项待设置的新值的缓冲区
				 optlen: optval缓冲区长度
* Return:        成功:返回 32位的网络序列IP地址
* 				 失败:返回 -1
* Others:        
*****************************************************/

optname

3.2 UDP广播

3.2.1 发送广播消息

1. 开广播权限:int on = 1;
//setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
2. 向广播地址发送消息

//搭建udp客户端
#include<stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

//广播发送: 1.开广播权限
//          2.向广播地址发送
int main(int argc,char *argv[])
{
    int sockfd,connfd;
    int ret;
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);         //创建套接字(UDP)

    /* 创建一张空卡addr */
    struct sockaddr_in addr,cliaddr;
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(7777);
    addr.sin_addr.s_addr = inet_addr("192.168.1.255");//填写广播地址

    
    //开广播权限
    int on = 1;
    setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));

    char buf[512];
    while(1)
	{
        
        fgets(buf,sizeof(buf),stdin);
        buf[strlen(buf)-1] = 0;
        if(*buf=='q')
		{
            break;
        }

        ret = sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&addr,sizeof(addr));
        if(ret==-1)
		{
            perror("sendto");
            exit(-1);
        }
    }

    close(sockfd);

    return 0;
}

3.2.2 接收广播消息

1.绑定广播地址或0地址

//搭建udp服务器
#include<stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
//广播接收: 1.绑定广播地址或0地址

int main(void)
{
    int sockfd,connfd;
    int ret;
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);         //创建套接字(UDP)

    /* 创建一张空卡addr */
    struct sockaddr_in addr,cliaddr;
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(7777);
    addr.sin_addr.s_addr = inet_addr("0"); //填写ip  (0:代表本机)

    ret = bind(sockfd,(struct sockaddr *)&addr,sizeof(addr)); //绑定地址信息
    if(ret==-1)
	{
        perror("bind");
        exit(-1);
    }
    
    char buf[512];

    int clilen = sizeof(cliaddr);
    
    while(1)
	{
        memset(buf,0,sizeof(buf));
        ret = recvfrom(sockfd, buf, sizeof(buf), 0 ,(struct sockaddr *)&cliaddr,&clilen);
        if(ret==-1)
		{
            break;
        }
        printf("ip:%s,port:%d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
        printf("recv:%d bytes,buf:%s\n",ret,buf);
        //解析客户端请求 根据请求给响应数据
        sendto(sockfd,buf, sizeof(buf), 0 , (struct sockaddr *)&cliaddr,sizeof(cliaddr));

    }

    close(sockfd);

    return 0;
}

3.3 UDP组播

3.3.1 发送组播消息

1.向组播地址发送 例如 224.100.100.100

//搭建udp客户端
#include<stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

//组播发送: 1.向组播地址发送
int main(int argc,char *argv[])
{
    int sockfd,connfd;
    int ret;
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);         //创建套接字(UDP)

    /* 创建一张空卡addr */
    struct sockaddr_in addr,cliaddr;
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(7777);
    addr.sin_addr.s_addr = inet_addr("224.100.100.100");//填写组播地址

    char buf[512];
    while(1)
	{
        
        fgets(buf,sizeof(buf),stdin);
        buf[strlen(buf)-1] = 0;
        if(*buf=='q')
		{
            break;
        }

        ret = sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&addr,sizeof(addr));
        if(ret==-1)
		{
            perror("sendto");
            exit(-1);
        }
    }

    close(sockfd);

    return 0;
}

3.3.2 接收组播消息

1.加入多播组
2.绑定组播地址或0地址
//搭建udp服务器
#include<stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
//组播接收: 1.绑定组播地址或0地址
//          2.加入多播组 

int main(void)
{
    int sockfd,connfd;
    int ret;
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);         //创建套接字(UDP)

    /* 创建一张空卡addr */
    struct sockaddr_in addr,cliaddr;
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(7777);
    addr.sin_addr.s_addr = inet_addr("0"); //填写ip  (0:代表本机)

    /* 通过setsockopt 加入多播组 */
    
    //加入多播组
    struct ip_mreqn mreq;
    memset(&mreq,0,sizeof(mreq));
    mreq.imr_multiaddr.s_addr = inet_addr("224.100.100.100");  //组播地址
    mreq.imr_address.s_addr   = inet_addr("0");
    
    setsockopt(sockfd,IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));


    ret = bind(sockfd,(struct sockaddr *)&addr,sizeof(addr)); //绑定地址信息
    if(ret==-1)
	{
        perror("bind");
        exit(-1);
    }
    
    char buf[512];

    int clilen = sizeof(cliaddr);
    
    while(1)
	{
        
        memset(buf,0,sizeof(buf));
        ret = recvfrom(sockfd, buf, sizeof(buf), 0 ,(struct sockaddr *)&cliaddr,&clilen);
        if(ret==-1)
		{
            break;
        }
        printf("ip:%s,port:%d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
        printf("recv:%d bytes,buf:%s\n",ret,buf);
        //解析客户端请求 根据请求给响应数据
        sendto(sockfd,buf, sizeof(buf), 0 , (struct sockaddr *)&cliaddr,sizeof(cliaddr));
    }

    close(sockfd);

    return 0;
}

4 并发服务器

4.1 多进程并发服务器

基本思路: 每连接一个客户,创建进程,子进程负责处理客户请求,父进程负责连接请求
/******************************
    > File Name    : TCP_Server.c
    > Author       : GK
    > Description  : 搭建TCP并发服务器
    > Created Time : 2020年11月03日 星期二 10时21分49秒
 *****************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>

int tcp_Init(const char *ip, int port);
int do_client(int connfd);
void kill_Z_fork(int signum);

int main(int argc, char *argv[])
{
	int tcp_sockfd = 0, tcp_connetfd = 0;
	int addrlen = 0;
	struct sockaddr_in clientaddr;

	signal(SIGCHLD, kill_Z_fork); 

	tcp_sockfd = tcp_Init("192.168.1.192", 8888);

	while (1)
	{
	 	addrlen = sizeof(clientaddr);
		 
		printf("等待连接\n");
		tcp_connetfd = accept(tcp_sockfd, (struct sockaddr *)&clientaddr, &addrlen); //	建立连接套接字 这个地方会阻塞
		//避免被信号打扰到,加一些判断,如果被打扰到,就continue 继续阻塞
		if(tcp_connetfd == -1)
		{
			if(tcp_connetfd == EINTR)		//避免被信号函数打扰
			{
				continue;
			}
			else							//正常的错误进行退出
			{
				perror("accept");
				exit(-1);
			}
			
		}
		printf("连接成功 tcp_connetfd:%d  IP: %s  PORT: %d\n", tcp_connetfd, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));


		pid_t pid = fork();
		if (pid == 0) //子进程负责处理 客户请求(connetfd)
		{
			close(tcp_sockfd);
			do_client(tcp_connetfd); //执行do_client
		}
		else if (pid > 0) // 父进程 负责连接请求 (socketfd)
		{
			close(tcp_connetfd);
			continue;
		}
		else
		{
			perror("fork");
			break;
		}
	}

	close(tcp_sockfd); // 关机

	return 0;
}
/**
 * TCP服务器的初始化
*/
int tcp_Init(const char *ip, int port)
{
	int sockfd = 0;
	int re = 0;

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1)
	{
		perror("socket");
		exit(-1);
	}
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip);

	re = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)); // 服务器绑定IP
	if (re == -1)
	{
		perror("bind");
		exit(-1);
	}

	re = listen(sockfd, 255); //(开机)一直处于监听状态
	if (re == -1)
	{
		perror("listen");
		exit(-1);
	}

	return sockfd;
}

/**
 * while(1)循环处理客户端的消息
 */
int do_client(int connfd) 						//处理客户请求
{
	char recv_buf[512];
	char send_buf[512];
	int ret;
	while (1)
	{
		memset(recv_buf, 0, sizeof(recv_buf));
		ret = recv(connfd, recv_buf, sizeof(recv_buf), 0); //这里会阻塞
		if (ret <= 0)
		{
			printf("客户端退出\n");
			perror("recv");

			close(connfd);  
			exit(0);  							//结束子进程
		}
		printf("recv:%dbytes,recv_buf:%s\n", ret, recv_buf);

		if (0 == strcmp(recv_buf, "get_time"))
		{
			time_t val = time(NULL);
			char *ptr = ctime(&val);
			strcpy(send_buf, ptr);
			send(connfd, send_buf, sizeof(send_buf), 0);
		}
	}
}
/**
 * 处理僵尸进程
*/
void kill_Z_fork(int signum)
{
	printf("捕捉到僵尸信号\n");
	while(waitpid(-1, NULL, WNOHANG) > 0); //处理僵尸进程
}

4.2 多线程并发服务器

基本思路: 每连接一个客户,创建线程,子线程负责处理客户请求,主线程负责连接请求
/******************************
    > File Name    : TCP_Server.c
    > Author       : GK
    > Description  : 搭建TCP并发服务器
    > Created Time : 2020年11月03日 星期二 10时21分49秒
 *****************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>
#include <pthread.h>


int tcp_Init(const char *ip, int port);
void *do_client(void *arg);
void *pthread_function1(void *arg);

int main(int argc, char *argv[])
{
	int tcp_sockfd = 0, tcp_connetfd = 0;
	int addrlen = 0;
	struct sockaddr_in clientaddr;
	pthread_t thread_id = 0;

	tcp_sockfd = tcp_Init("192.168.1.192", 8888);

	while (1)
	{
		addrlen = sizeof(clientaddr);

		printf("等待连接\n");
		tcp_connetfd = accept(tcp_sockfd, (struct sockaddr *)&clientaddr, &addrlen); //	建立连接套接字 这个地方会阻塞
		//避免被信号打扰到,加一些判断,如果被打扰到,就continue 继续阻塞
		if (tcp_connetfd == -1)
		{
			perror("accept");
			exit(-1);
		}
		printf("连接成功 tcp_connetfd:%d  IP: %s  PORT: %d\n", tcp_connetfd, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
		
		pthread_create(&thread_id, NULL, do_client, (void *)&tcp_connetfd);
		
	}

	close(tcp_sockfd); // 关机

	return 0;
}

/**
 * TCP服务器的初始化
*/
int tcp_Init(const char *ip, int port)
{
	int sockfd = 0;
	int re = 0;

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1)
	{
		perror("socket");
		exit(-1);
	}
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip);

	re = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)); // 服务器绑定IP
	if (re == -1)
	{
		perror("bind");
		exit(-1);
	}

	re = listen(sockfd, 255); //(开机)一直处于监听状态
	if (re == -1)
	{
		perror("listen");
		exit(-1);
	}

	return sockfd;
}

/**
 * while(1)循环处理客户端的消息
 */
void* do_client(void *arg) //处理客户请求
{
	pthread_detach(pthread_self());
	
	int  connfd = *((int*)arg); 
	char recv_buf[512];
	char send_buf[512];
	int ret;
	while (1)
	{
		memset(recv_buf, 0, sizeof(recv_buf));
		ret = recv(connfd, recv_buf, sizeof(recv_buf), 0); //这里会阻塞
		if (ret <= 0)
		{
			printf("客户端退出\n");
			perror("recv");

			close(connfd);
			break;
		}
		printf("recv:%dbytes,recv_buf:%s\n", ret, recv_buf);

		if (0 == strcmp(recv_buf, "get_time"))
		{
			time_t val = time(NULL);
			char *ptr = ctime(&val);
			strcpy(send_buf, ptr);
			send(connfd, send_buf, sizeof(send_buf), 0);
		}
	}

	pthread_exit(NULL);
}

5 I/O模型

阻塞: 		最常用、最简单、效率最低
非阻塞: 	可防止进程阻塞在I/O操作上,需要轮询
io多路复用:允许同时对多个I/O进行控制
信号驱动:	一种异步通信模型

5.1 IO多路复用模拟并发服务器

5.1.1 select(框架)


//多线程TCP并发服务器
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>
#include <pthread.h>
#include <sys/time.h>

int tcp_init(const char *ip, int port);

int main(void)
{
    int sockfd    = 0;
    int connfd    = 0;
    int re        = 0;
    char buf[512] = {0};
    
    sockfd = tcp_init("0", 8089);
    if (sockfd == -1)
    {
        exit(-1);
    }

    int maxfd = sockfd; //记录最大文件描述符
    fd_set fd_read, tmpfs;
    FD_ZERO(&fd_read);
    FD_SET(sockfd, &fd_read); //添加文件描述符

    tmpfs = fd_read;
    while (1)
    {
        fd_read = tmpfs; //修正
        re = select(maxfd + 1, &fd_read, NULL, NULL, NULL);
        if (re == -1)
        {
            perror("select");
            exit(-1);
        }
        
        int fd = 0;
        for (fd = sockfd; fd <= maxfd; fd++)
        {
            if (FD_ISSET(fd, &fd_read))//printf("有事件产生\n");
            {
                if (fd == sockfd)
                {   
                    //printf("连接请求\n");
                    int connfd = accept(sockfd, NULL, NULL);
                    printf("connect a client :%d\n", connfd);

                    FD_SET(connfd, &tmpfs); //添加connfd

                    if (maxfd < connfd)
                    {
                        maxfd = connfd; //修正maxfd
                    }
                }
                else
                {
                    //printf("客户请求\n");
                    memset(buf, 0, sizeof(buf));
                    re = recv(fd, buf, sizeof(buf), 0);
                    if (re <= 0)
                    {
                        printf("客户退出 %d\n", fd);
                        close(fd);
                        FD_CLR(fd, &tmpfs); //从表中清除
                        continue;
                    }
                    printf("recv:%s\n", buf);
                }
            }
        }
    }
    return 0;
}
int tcp_init(const char *ip, int port) //tcp服务器初始化
{
    int sockfd;
    int re;
    sockfd = socket(AF_INET, SOCK_STREAM, 0); //创建套接字(TCP)

    /* 创建一张空卡addr */
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip); //填写自己的ip

    re = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)); //绑定地址信息
    if (re == -1)
    {
        perror("bind");
        return -1;
    }

    listen(sockfd, 50); //设置监听队列 队列长度32

    return sockfd;
}

5.1.2 poll(框架)

以下代码借鉴:Linux网络编程——tcp并发服务器(poll实现)

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#define OPEN_MAX 100
 
int main(int argc, char *argv[])
{
	//1.创建tcp监听套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	
	//2.绑定sockfd
	struct sockaddr_in my_addr;
	bzero(&my_addr, sizeof(my_addr));
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(8000);
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
	
	//3.监听listen
	listen(sockfd, 10);
	
	//4.poll相应参数准备
	struct pollfd client[OPEN_MAX];
	int i = 0, maxi = 0;
	for(;i<OPEN_MAX; i++)
		client[i].fd = -1;//初始化poll结构中的文件描述符fd
	
	client[0].fd = sockfd;//需要监测的描述符
	client[0].events = POLLIN;//普通或优先级带数据可读
	
	//5.对已连接的客户端的数据处理
	while(1)
	{
		int ret = poll(client, maxi+1, -1);//对加入poll结构体数组所有元素进行监测
		
		//5.1监测sockfd(监听套接字)是否存在连接
		if((client[0].revents & POLLIN) == POLLIN )
		{
			struct sockaddr_in cli_addr;
			int clilen = sizeof(cli_addr);
			int connfd = 0;
			//5.1.1 从tcp完成连接中提取客户端
			connfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen);
			
			//5.1.2 将提取到的connfd放入poll结构体数组中,以便于poll函数监测
			for(i=1; i<OPEN_MAX; i++)
			{
				if(client[i].fd < 0)
				{
					client[i].fd = connfd;
					client[i].events = POLLIN;
					break;
				}
			}
			
			//5.1.3 maxi更新
			if(i > maxi)
				maxi = i;
				
			//5.1.4 如果没有就绪的描述符,就继续poll监测,否则继续向下看
			if(--ret <= 0)
				continue;
		}
		
		//5.2继续响应就绪的描述符
		for(i=1; i<=maxi; i++)
		{
			if(client[i].fd < 0)
				continue;
			
			if(client[i].revents & (POLLIN | POLLERR))
			{
				int len = 0;
				char buf[128] = "";
				
				//5.2.1接受客户端数据
				if((len = recv(client[i].fd, buf, sizeof(buf), 0)) < 0)
				{
					if(errno == ECONNRESET)//tcp连接超时、RST
					{
						close(client[i].fd);
						client[i].fd = -1;
					}
					else
						perror("read error:");
					
				}
				else if(len == 0)//客户端关闭连接
				{
					close(client[i].fd);
					client[i].fd = -1;
				}
				else//正常接收到服务器的数据
					send(client[i].fd, buf, len, 0);
				
				//5.2.2所有的就绪描述符处理完了,就退出当前的for循环,继续poll监测
				if(--ret <= 0)
					break;
				
			}
		}
	}
	return 0;
}

5.1.3 epoll(框架)

以下代码借鉴:Linux网络编程——tcp并发服务器(epoll实现)

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#define OPEN_MAX 100
 
int main(int argc, char *argv[])
{
	struct epoll_event event;   // 告诉内核要监听什么事件  
    struct epoll_event wait_event; //内核监听完的结果
	
	//1.创建tcp监听套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	
	//2.绑定sockfd
	struct sockaddr_in my_addr;
	bzero(&my_addr, sizeof(my_addr));
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(8001);
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
	
	//3.监听listen
	listen(sockfd, 10);
	 
	//4.epoll相应参数准备
	int fd[OPEN_MAX];
	int i = 0, maxi = 0;
	memset(fd,-1, sizeof(fd));
	fd[0] = sockfd;
	
	int epfd = epoll_create(10); // 创建一个 epoll 的句柄,参数要大于 0, 没有太大意义  
    if( -1 == epfd ){  
        perror ("epoll_create");  
        return -1;  
    }  
      
    event.data.fd = sockfd;     //监听套接字  
    event.events = EPOLLIN; // 表示对应的文件描述符可以读
	
	//5.事件注册函数,将监听套接字描述符 sockfd 加入监听事件  
    int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);  
    if(-1 == ret){  
        perror("epoll_ctl");  
        return -1;  
    } 
	
	//6.对已连接的客户端的数据处理
	while(1)
	{
		// 监视并等待多个文件(标准输入,udp套接字)描述符的属性变化(是否可读)  
        // 没有属性变化,这个函数会阻塞,直到有变化才往下执行,这里没有设置超时   
        ret = epoll_wait(epfd, &wait_event, maxi+1, -1); 
		
		//6.1监测sockfd(监听套接字)是否存在连接
		if(( sockfd == wait_event.data.fd )   
            && ( EPOLLIN == wait_event.events & EPOLLIN ) )
		{
			struct sockaddr_in cli_addr;
			int clilen = sizeof(cli_addr);
			
			//6.1.1 从tcp完成连接中提取客户端
			int connfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen);
			
			//6.1.2 将提取到的connfd放入fd数组中,以便下面轮询客户端套接字
			for(i=1; i<OPEN_MAX; i++)
			{
				if(fd[i] < 0)
				{
					fd[i] = connfd;
					event.data.fd = connfd; //监听套接字  
					event.events = EPOLLIN; // 表示对应的文件描述符可以读
					
					//6.1.3.事件注册函数,将监听套接字描述符 connfd 加入监听事件  
					ret = epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &event);  
					if(-1 == ret){  
						perror("epoll_ctl");  
						return -1;  
					} 
					
					break;
				}
			}
			
			//6.1.4 maxi更新
			if(i > maxi)
				maxi = i;
				
			//6.1.5 如果没有就绪的描述符,就继续epoll监测,否则继续向下看
			if(--ret <= 0)
				continue;
		}
		
		//6.2继续响应就绪的描述符
		for(i=1; i<=maxi; i++)
		{
			if(fd[i] < 0)
				continue;
			
			if(( fd[i] == wait_event.data.fd )   
            && ( EPOLLIN == wait_event.events & (EPOLLIN|EPOLLERR) ))
			{
				int len = 0;
				char buf[128] = "";
				
				//6.2.1接受客户端数据
				if((len = recv(fd[i], buf, sizeof(buf), 0)) < 0)
				{
					if(errno == ECONNRESET)//tcp连接超时、RST
					{
						close(fd[i]);
						fd[i] = -1;
					}
					else
						perror("read error:");
				}
				else if(len == 0)//客户端关闭连接
				{
					close(fd[i]);
					fd[i] = -1;
				}
				else//正常接收到服务器的数据
					send(fd[i], buf, len, 0);
				
				//6.2.2所有的就绪描述符处理完了,就退出当前的for循环,继续poll监测
				if(--ret <= 0)
					break;
			}
		}
	}
	return 0;
}

6 网络超时检测

6.1 *通过setsockopt()函数,设置接收超时

#include <sys/types.h>       
#include <sys/socket.h>

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
/**************************************************** 
* Description:  用于任意类型、任意状态套接口的设置选项值
* Input:         sockfd:socket套接字
* 				 level: 选项定义的层次
				 optname:需设置的选项
				 optval: 指向存放选项待设置的新值的缓冲区
				 optlen: optval缓冲区长度
* Return:        成功:返回 32位的网络序列IP地址
* 				 失败:返回 -1
* Others:        
*****************************************************/

optname
下面的例程实现的功能是:

  1. 使用fork()函数实现的多进程并发服务器
  2. 使用setsockopt()函数实现接收超时机制
  3. struct timeval tv = {3,0};
  4. setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>

int do_client(int confd, struct sockaddr_in addr);
int tcp_init(const char *ip);

void do_signal(int sig)  // 处理大量的僵尸进程
{
    while (waitpid(-1, NULL, WNOHANG) > 0)
        ;
}

int main(int argc, char const *argv[])
{
    int sock_fd, connd_fd;
    pid_t pid;
    struct sockaddr_in cli_addr;

    signal(SIGCHLD, do_signal);		// 捕获僵尸信号

    if ((sock_fd = tcp_init("0")) == -1)	// socket ——> TCP初始化
    {
        exit(-1); 
    }

    while (1)
    {
        int cli_len = sizeof(cli_addr);
        connd_fd = accept(sock_fd, (struct sockaddr *)&cli_addr, &cli_len);		// 获取已连接的客户端的信息结构体
        if (connd_fd == -1)
        {
            if (errno == EINTR) // 避免被信号中断打扰
            {
                continue;
            }
            else	// 如果是函数返回的错误
            {
                perror("accept");
                exit(-1);
            }
        }

        struct timeval timeout;   // 设置超时时间结构体  需要包含头文件#include <sys/time.h>
        timeout.tv_sec  = 3; //3秒
        timeout.tv_usec = 0;

        
        // 设置接收超时机制
        setsockopt(connd_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
        printf("accept done.\n");

        pid = fork();  
        if (pid == 0) //fork会复制两份数据
        {
            close(sock_fd); // 子进程不使用sock_fd, 所以要关闭
            do_client(connd_fd, cli_addr);
        }
        else if (pid > 0)
        {
            close(connd_fd); // 父进程不使用connd_fd, 所以要关闭
            continue;
        }
        else
        {
            perror("fork");
            exit(-1);
        }

        close(connd_fd);
    }
    close(sock_fd);
    return 0;
}

int do_client(int confd, struct sockaddr_in addr) // 处理客户发来的数据
{
    char buf[128] = {0};
    ssize_t ret = 0;

    while (1)
    {
        memset(buf, 0, sizeof(buf));
        ret = recv(confd, buf, sizeof(buf), 0);
        if (ret == 0)
        {
            perror("recv");
            printf("客户端退出  IP: %s, Port: %d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
            break;
        }
        else if(ret == -1)
        {
            if((errno == EAGAIN) || (errno == EWOULDBLOCK))
            {
                printf("时间超时\n");
                //continue;
                break;
            }
            else
            {
                exit(0);
            }
            
        }
        
        printf("客户端登入 IP: %s, Port: %d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
        printf("buf is :%s\n", buf);

        if (send(confd, buf, sizeof(buf), 0) < 0)
        {
            perror("send");
            break;
        }
    }
    return 0;
}

int tcp_init(const char *ip)
{
    int sockfd = 0;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        perror("socket");
        return -1;
    }
    printf("socket done.\n");
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(6665);
    addr.sin_addr.s_addr = inet_addr(ip);

    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    {
        perror("bind");
        return -1;
    }
    printf("bind done.\n");
    if (listen(sockfd, 255) == -1)
    {
        perror("listen");
        return -1;
    }
    printf("listen done.\n");

    return sockfd;
}

6.2 在select函数中,设置超时时间

struct timeval tv; // 描述时间的结构体变量
fdset rdfds; // 定义读描述符集合
……
while(1)
{
	tv.tv_sec = 8;
	tv.tv_usec = 0;
	FD_ZERO(&rdfds);
	FD_SET(sockfd, &rdfds);
	if (select(sockfd+1, &rdfds, NULL, NULL, &tv) == 0)
	{
	        超时处理
	}
}
……

6.3 通过alarm函数设置闹钟,捕获闹钟

这种方法的原理是在从套接字接收数据之前先设置8秒钟的定时器。如果8秒钟内没有数据到来,内核产生的SIGALRM信号会中断。

void handler(int signo) // 自定义SIGALRM信号处理函数
{
        return;
}
struct sigaction act; // 描述信号行为的变量
……
sigaction(SIGALRM, NULL, &act); // 获取SIGALRM信号的属性

act.sa_handler = handler; // 设置SIGALRM信号的处理函数
act.sa_flags &= ~SA_RESTART; // 关闭重启被中断操作的选项

sigaction(SIGALRM, &act, NULL); // 设置SIGALRM信号的属性
alarm(8); // 设置8秒钟的定时器
……

7 数据库

7.1 在线安装SQlite3数据库

sudo apt-get install sqlite3

7.2 数据库的基本使用

7.2.1 创建数据库 .db

sqlite3 student.db

7.2.2 系统命令:以 ‘.’ 开头

	.help	帮助手册
	.exit   退出
	.quit   退出
	.open   打开
	.schema 查看表的结构
	.databases   查看打开的数据库
	.table	查看当前数据库下 已经有的表格

7.2.3 基本命令:以 ‘;’ 结尾

7.2.3.1 创建记录表
	create table stu(id int, name char, score int);
	//创建一张列表 列表名:stu  列表内id是ine型 ...
7.2.3.2 插入记录表
	//完整的插入:
	insert into stu values(1001, "zhangsan", 80);
	insert into stu values(1002, "lisi", 50);
	
	//部分插入
	insert into stu (name, score) values("kusu", 30); 
7.2.3.3 查看记录表
	//查看表格中的内容   '*'代表的是全部内容
	select * from stu;
	
	//查看表格中的 部分 字段
	select name from stu;
	select name,score from stu;
	
	//查看score等于80的
	select * from stu where score=80;
	
	//查看score=80并且id=1001的
	select * from stu where score=80 and id=1001;
	
	//查看score=40或者id=1002的
	select * from stu where score=40 and id=1002;
7.2.3.4 删除记录表
	//删除整个记录表中的内容,但是文件还在
	delete from stu;
	
	//删除一条记录
	delete from stu where score=40;
7.2.3.5 更新一条记录
	update stu set name="zhangsan" where id=1001;
	update stu set name="zhangsan" , score=80 where id=1001;
7.2.3.6 插入一列
	alter table stu add colimn address char;
7.2.3.7 删除一列
	sqlite3 不支持,直接删除一列
	
	所以分三个步骤去做:
		1.创建一张新的表
		create table stu1 as select id, name, score from stu;
		2.删除原有的表
		drop table stu;
		3.将新的表名字改成原有的旧表的名字
		alter table stu1 rename to stu;

7.3 使用C语言API接口

还有其他的函数接口,需要去官网查看,以下是基本的函数:

int sqlite3_open(char *path, sqlite3 **db);
/************************************
	功能:打开sqlite3数据库
	参数:
		path:数据库文件的路径
		db:指向sqlite句柄的指针
	返回值:
		成功:SQLITE_OK
		失败:返回错误码
***********************************/
int sqlite3_close(sqlite3 *db);
/************************************
	功能:关闭数据库
	返回值:
		成功:SQLITE_OK
		失败:返回错误码
	
	const char *sqlite3_errmg(sqlite3 *db);
	返回值:返回错误信息
***********************************/
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值