TCP IP网络编程

news2024/11/23 11:52:10

文章目录

  • TCP IP网络编程
    • 一、基础知识(TCP)
      • 1)Linux
        • 1. socket()
        • 2.bind()
          • 2.1前提
          • 2.2字节序与网络字节序
          • 2.3 字节序转换
          • 2.4 字符串信息转化成网络字节序的整数型
          • 2.5 INADDR_ANY
        • 3.listen()
        • 4.accept()
        • 5.connect()
        • 6.案例小结
          • 6.1服务器端
          • 6.2 客户端
        • 7.半关闭
      • 2)Window
        • 1. socket()
        • 2.bind()
        • 3.listen()
        • 4.accept()
        • 5.recv()
        • 6.send()
    • 二、UDP基础知识
      • 1)Linux
        • 1.sendto()
        • 2.recvfrom()
        • 3.已连接UDP套接字
        • 4.案例
      • 2)Window
    • 三、域名
      • 1.由域名得到ip
      • 2.通过IP地址获取域名
      • 3.Window
    • 四、套接字的多种选项
      • 1.获取和设置套接字选项
      • 2.各种选项
      • 3.Nagle算法
    • 五、多进程服务端
      • 1.案例
      • 2.分割I/O
    • 六、进程间通信
      • 1.基于管道(PIPE)的通信
    • 七、并发服务器
      • 1.I/O复用
        • 1.1 select()
        • 1.2.epoll()
          • 1.2.2 案例
          • 1.2.3边沿触发与条件触发
      • 2.多进程实现并发
      • 3.多线程服务器端
        • 3.1 创建线程
        • 3.2 等待线程的消亡
        • 3. 3 互斥量
        • 3.4 信号量
    • 八、多种I/O函数
      • 1.send(),recv()
      • 2.readv(),writev()
    • 九、多播和广播
      • 1.设置TTL
      • 2.加入多播组
      • 3.广播
    • 十、Linux编程
      • 1.标准IO与系统IO之间的相互转化
      • 2.复制文件描述符dup
      • 3.实现半关闭

TCP IP网络编程

一、基础知识(TCP)

1)Linux

1. socket()
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
/*
domain: 套接字中使用的协议族信息
type : 套接字数据传输类型信息
protocol :计算机间通信中使用的协议信息。【最终决定采用什么协议】
*/
int socket(int domain, int type, int protocol);

domain

名称描述
PF_INETIPv4互联网协议族
PF_INET6IPv6互联网协议族
PF_LOCAL本地通信的UNIX协议族
PF_PACKET底层套接字的协议族
PF_IPXIPX Novell协议族

type

  1. 面向连接的套接字(SOCK_STREAM) 【跟TCP一样】

特点:可靠的,按序传递的,基于字节的面向连接的数据传输方式的套接字。

  1. 面向消息的套接字(SOCK_DGRAM) 【跟UDP一样】

特点:不可靠的,不按序传递的,以数据的高速传输为目的的套接字。

protocol

传递前两个参数即可创建所需套接字,第三个参数是为了以下情况:

同一协议族中存在多个数据传输方式相同的的协议。这时需要通过第三个参数具体指定协议信息。

IPv4协议族中面向连接的套接字,协议只有IPPROTO_TCP。

int tcp_socket = socket(PF_INET, SOCK_STREAM, 			      						IPPROTO_TCP);

这个套接字称为TCP套接字。

IPv4协议族中面向消息的套接字,协议只有IPPROTO_UDP

int udp_socket = socket(PF_INET, SOCK_STREAM, 			      						IPPROTO_UDP);

这个套接字称为UDP套接字。

2.bind()
2.1前提
struct sockaddr_in
{
    sa_family_t		sin_family; //地址族
    uint16_t		sin_port;   //16位TCP/UDP端口号
    struct in_addr	sin_addr;	//32位IP地址
    char			sin_zero[8];//不使用
}

struct in_addr
{
    In_addr_t 	s_addr;		//32位IPv4地址
}

sin_family

地址族含义
AF_INETIPv4网络协议中使用的地址族
AF_INET6IPv6网络协议中使用的地址族
AF_LOCAL本地通信中采用的UNIX协议的地址族

sin_port

它以网络字节序保存。

sin_addr

以网络字节序保存。

sin_zero

无特殊含义。只是为了使结构体sockaddr_in的大小与sockaddr结构体保持一致而插入的成员。必须填充为0,否则无法得到想要的结果。

 struct sockaddr 
 {
      sa_family_t  sa_family; //地址族
       char        sa_data[14]; //地址信息
}

此结构体成员sa_data保存的地址信息中需包含IP地址和端口号,剩余部分应填充0,这也是bind函数要求的。而这对于包含地址信息来讲非常麻烦,继而就有了新的结构体sockaddr_in。若按照之前的讲解填写sockaddr_in结构体,则将生成符合bind函数要求的字节流。最后转换为sockaddr型的结构体变量,再传递给bind函数即可。

2.2字节序与网络字节序

CPU向内存保存数据的方式有2种

  1. 大端序:高位字节存放到低地址。
  2. 小端序:高位字节存放到高位地址。

因为这种情况,所以网络传输时规定了统一的字节序。大端序,这就叫做网络字节序。

传输数据时,先把数据数组转化成大端序格式再进行网络传输。

2.3 字节序转换
unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);

解释:

  • htons中的h代表主机(host)字节序。
  • htons中的n代表网络(network)字节序。
  • s指的是short
  • l值的是long

htons的含义:把short型数据从主机字节序转化为网络字节序。

ntohs的含义:把short型数据从网络字节序转化成主机字节序。

通常,以s作为后缀的函数中,s代表2个字节shont,因此用于端口号转换;以1作为后缀的函数中,1代表4个字节,因此用于IP地址转换。

注意:除了向sockaddr in结构体变量填充数据外,其他情况无需考虑字节序问题。

2.4 字符串信息转化成网络字节序的整数型
#include <arpa/inet.h>

int_addr_t inet_addr(const char* string);
/* 把点分十进制ip地址,转化为32位整型数值,并按照网络字节序*/

成功返回32为整型数值,失败返回INADDR_NONE

同样功能的函数还有:

#include <arpa/inet.h>

int inet_aton(const char* string, struct in_addr* addr);

实际编程中若要调用inet_addr函数,需将转换后的IP地址信息代入sockaddr_in结构体中声明的in_addr结构体变量。而inet_aton函数则不需此过程。原因在于,若传递in_addr结构体变量地址值,函数会自动把结果填人该结构体变量。

char* addr = "127.232.124.79";
struct sockaddr_in addr_inet;
inet_acton(addr,&addr_inet.sin_addr);

将32为数值转化为字符串

#include <arpa/inet.h>
char* inet_ntoa(struct in_addr adr);

注意!!!

该函数将通过参数传入的整数型IP地址转换为字符串格式并返回。但调用时需小心,返回值类型为char指针。返回字符串地址意味着字符串已保存到内存空间,但该函数未向程序员要求分配内存,而是在内部申请了内存并保存了字符串。也就是说,调用完该函数后,应立即将字符串信息复制到其他内存空间。因为,若再次调用inet_ntoa函数,则有可能覆盖之前保存的字符串信息。总之,再次调用inet_ntoa函数前返回的字符串地址值是有效的。若需要长期保存,则应将字符串复制到其他内存空间。

2.5 INADDR_ANY

每次创建服务器端套接字都要输入IP地址会有些繁琐,所以采用常数INADDR_ANY分配服务器端的IP地址,可自动获取运行服务器端的计算机IP地址,不必亲自输入。

addr.sin_addr.s_addr = htonl(INADDR_ANY);

2.6 bind()

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
/*
*/
int bind(int sockfd, const struct sockaddr *addr,
          socklen_t addrlen);

成功返回0,失败返回-1

3.listen()
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
/*
sockfd : 希望进入等待连接请求状态的套接字文件描述符
backlog:连接请求等待队列的长度。
*/
int listen(int sockfd, int backlog);
4.accept()
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
/*
sock:服务器套接字的文件描述符
addr:保存发起连接请求的客户端地址信息的变量地址值,调用函数后向传递来的地址变量参数填充客户端地址信息。
addrlen:第二个参数addr结构体的长度,但是存有长度的变量地址。函数调用完成后,该变量即被填入客户端地址长度。
*/
int accept(int sockfd, struct sockaddr *addr, 
           socklen_t *addrlen);
5.connect()
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

/*
sockfd:客户端套接字文件描述符
addr:保存目标服务器端地址信息的变量地址值
addrlen:以字节为单位传递地址变量长度
*/
int connect(int sockfd, const struct sockaddr *addr,
            socklen_t addrlen);

注意

客户端的IP地址和端口在调用connect函数时自动分配,无需调用bind函数进行分配。IP用计算机的IP,端口随机。

6.案例小结
6.1服务器端
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cstring>
#include <arpa/inet.h>

void error_handling(const char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

int main(int argc, char** argv)
{
    if(argc != 2)
    {
        printf("Usage : %s <port> \n",argv[1]);
        exit(1);
    }

    // 创建套接字
    int serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock < 0)
    {
        error_handling("serv_sock() error");
    }

    struct sockaddr_in serv_addr;
    // 初始化地址
    memset(&serv_addr,0,sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));

    // 分配地址
    int ret = bind(serv_sock,(struct sockaddr*)&serv_addr, sizeof(serv_addr));

    if(ret < 0)
    {
        error_handling("bind() error");
    }

    // 开始监听
    int ret1 = listen(serv_sock, 5);
    if(ret1 < 0)
    {
        error_handling("listen() error");
    }

    int client_sock;
    struct sockaddr_in client_addr;

    socklen_t client_addr_size = sizeof(client_addr);

    // 接受访问
    client_sock = accept(serv_sock, (struct sockaddr*) &client_addr, &client_addr_size);
    if(client_sock < 0)
    {
        error_handling("accept() error");
    }

    char message[] = "hello,world!";

    // 传输数据
    write(client_sock, message, sizeof(message));

    // 传输完成,关闭套接字
    close(client_sock);
    close(serv_sock);

    return 0;
}


6.2 客户端
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>

void error_handling(const char* message)
{
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}

int main(int argc, char** argv)
{
    if(argc != 3)
    {
        printf("Usage : %s <ip> <port> \n", argv[0]);
        exit(1);
    }
    int sock(-1);

    // 创建套接字
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(sock < 0)
    {
        perror("sock()");
        // error_handling("socket() error");
    }

    // 初始化地址和端口号
    struct sockaddr_in serv_addr;

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    // 发送请求
    int ret = connect(sock, (struct sockaddr*)&serv_addr,sizeof(serv_addr));
    if(ret < 0)
    {
        perror("connect()");
        // error_handling("connect() error");
        exit(1);
    }

    char message[30];
    // 接收数据
    int str_len = read(sock,message,sizeof(message)-1);
    if(str_len < 0)
    {
        perror("read()");
        // error_handling("read() error");
    }
    printf("Message from server : %s \n", message);

    // 关闭套接字
    close(sock);

    return 0;
}

7.半关闭

Linux的close函数意味着完全断开连接。完全断开不仅指无法传输数据,而且也不能接收数据。

开一部分连接是指,可以传输数据但无法接收,或可以接收数据但无法传输。顾名思义就是只关闭流的一半。

#include <sys/socket.h>

int shutdown(int sockfd, int how);

how

名称含义
SHUT_RD断开输入流
SHUT_WR断开输出流
SHUT_RDWR同时断开I/O流

2)Window

1. socket()
#include <winsock2.h>
SOCKET socket(int af, int type, int protocol);

这些参数与Linux一样,只是返回值不同。SOCKET其实就是整数类型,微软把他重定义了。

出现错误时,返回INVALID_SOCKET。

#include <winsock2.h>
SOCKET soc = socket(PF_INET, SOCK_STREAM,IPPROTO_TCP);
if(soc == INVALID_SOCKET)
    errorHandling("........");
2.bind()

与Linux完全相同。但是,Windows中不存在inet aton函数

3.listen()

同Linux

4.accept()

同Linux

5.recv()

同linux的read()

6.send()

同Linux的write()

二、UDP基础知识

1)Linux

1.sendto()
#include <sys/types.h>
#include <sys/socket.h>
/*

sockfd : 用于传输数据的UDP套接字文件描述符
buff : 保存待传输数据的换成地址值
nbytes :待传输的数据长度,以字节为单位
flags :可选参数,若没有传递0
to : 存有目标地址信息的sockaddr结构体变量的地址值
addrlen :传递给参数to的地址值结构体变量长度
*/
ssize_t sendto(int sockfd, const void *buf, size_t len, 				int flags,const struct sockaddr 						*dest_addr, socklen_t addrlen);

成功返回传输的字节数,失败返回-1.

调用该函数时自动分配IP和端口号

2.recvfrom()
#include <sys/types.h>
#include <sys/socket.h>
/*
sockfd : 用于接收数据的UDP套接字文件描述符
buff : 保存接收数据的缓冲地址值
nbytes : 可接收的最大字节数,故无法超过参数buff所指的缓冲大小
flags : 可选参数,若没有则传入0
src_addr : 存有发送端地址信息的地址值
addrlen :保存参数from的结构体变量长度的变量地址值
*/
ssize_t recvfrom(int sockfd, void *buf, size_t len, 
                 int flags,
 				 struct sockaddr *src_addr,
                 socklen_t *addrlen);
3.已连接UDP套接字

如果对于某个目标,需要一直发送消息,那么就可以使用已连接的UDP。

和TCP套接字一样,使用connect函数,使用这个函数之后,就可以使用write和read函数

4.案例

服务器端

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>

const int BUF_SIZE = 30;

int main(int argc, char** argv)
{
    // 判断
    if(argc != 2)
    {
        fprintf(stderr,"Usage : %s <port> \n",argv[0]);
        exit(1);
    }
    int serv_sock(-1);
    // 创建套接字
    serv_sock = socket(PF_INET,SOCK_DGRAM,0);
    if(serv_sock < 0)
    {
        perror("socket()");
        exit(1);
    }
    // 初始化IP地址和端口号
    struct sockaddr_in sock_addr;
    memset(&sock_addr, 0, sizeof(sock_addr));
    sock_addr.sin_family = AF_INET; //地址族
    sock_addr.sin_addr.s_addr = htonl(INADDR_ANY);//ip地址自动获取本机ip
    sock_addr.sin_port = htons(atoi(argv[1]));

    // 分配ip和端口号
    int ret = bind(serv_sock, (struct sockaddr*)&sock_addr, sizeof(sock_addr));
    if(ret < 0)
    {
        perror("bind()");
        exit(1);
    }

    // 初始化客户请求
    int client_sock(-1);
    struct sockaddr_in client_addr;
    socklen_t client_addr_len = sizeof(client_addr);

    // 保存客户端发来的信息
    char message[BUF_SIZE];
    int str_len(0);

    while(true)
    {
        // 接收数据
        str_len = recvfrom(serv_sock, message, BUF_SIZE, 0,
                          (struct sockaddr*)&client_addr, 
                          &client_addr_len);

        // 发送数据
        sendto(serv_sock, message, str_len, 0,
        (struct sockaddr*)&client_addr, client_addr_len);
        

    }
    close(serv_sock);
    return 0;

}

客户端

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

const int BUF_SIZE = 30;

int main(int argc, char** argv)
{
    if(argc != 3)
    {
        printf("Usage : %s <ip> <port> \n", argv[0]);
        exit(1);
    }

    // 创建套接字
    int clt_sock(-1);
    clt_sock = socket(PF_INET, SOCK_DGRAM, 0);
    if(clt_sock < 0)
    {
        perror("socket()");
        exit(1);
    }
    // 初始化服务器地址
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    char message[BUF_SIZE];

    struct sockaddr_in from_addr;

    memset(&from_addr, 0, sizeof(from_addr));
    socklen_t from_addr_len = sizeof(from_addr);

    while (true)
    {
        fputs("Input message(Q to quit): ",stdout);
        fgets(message, BUF_SIZE, stdin);

        if(!strcmp(message,"q\n") || !strcmp(message, "Q\n"))
            break;
        
        // write(clt_sock, message, strlen(message));
        // 发送数据
        sendto(clt_sock, message, strlen(message), 0,
              (struct sockaddr*)&serv_addr, sizeof(serv_addr));

        // int str_len = read(clt_sock, message, BUF_SIZE -1);
        // message[str_len] = 0;

        // 接收数据
        int len = recvfrom(clt_sock, message, BUF_SIZE, 0,
                          (struct sockaddr*)&from_addr, &from_addr_len);
        
        message[len] = 0;
        printf("Message from server : %s", message);

    }
    
    close(clt_sock);
    return 0;
}

2)Window

同Linux一模一样。

三、域名

1.由域名得到ip

#include <netdb.h>
extern int h_errno;

/* 通过域名获取ip地址*/
struct hostent *gethostbyname(const char *name);

struct hostent 
{
    char  *h_name;            /* official name of host */
    char **h_aliases;         /* alias list */
    int    h_addrtype;        /* host address type */
    int    h_length;          /* length of address */
    char **h_addr_list;       /* list of addresses */
}

成功返回结构体类型,失败返回NULL

  • h_name :该变量中存有官方域名
  • h_aliases :可以通过多个域名访问同一个主页。
  • h_addrtype :保存地址族信息
  • h_length : 保存IP地址长度
  • h_addr_list : 保存域名1对应的IP地址

2.通过IP地址获取域名

#include <sys/socket.h>       /* for AF_INET */
#include <netdb.h>

struct hostent *gethostbyaddr(const void *addr,
                              socklen_t len, 
                              int type);
  • addr : 含有IP地址信息的in_addr结构体的指针。
  • len :向第一个参数传递的地址信息的字节数,IPv4时为4,IPv6时为16
  • type:传递地址族信息,IPv4时为AF_INET,IPv6时为IP_INET6

3.Window

同Linux一样,连函数名和参数都相同。

四、套接字的多种选项

1.获取和设置套接字选项

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int getsockopt(int sockfd, int level, int optname,
                void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
                const void *optval, socklen_t optlen);
  • sockfd :用于查看选项套接字文件描述符
  • Level :要查看的可选项的协议层
  • optname :要查看的可选项名
  • optval :保存查看结果的缓冲地址值
  • optlen :向第四个参数optval传递的缓冲大小。调用函数后,该变量中保存通过第四个参数返回的可选项信息的字节数。

2.各种选项

直接man函数,查看选项。或者上网搜索。

3.Nagle算法

为防止因数据包多过而发生网络过载。

在这里插入图片描述

五、多进程服务端

1.案例

在这里插入图片描述

服务器端

/**
 * 多进程并发服务器
 */
#include <cstdio>
#include <cstdlib>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <cstring>

const int BUF_SIZE = 30;
void read_childproc(int sig)//声明信号处理函数
{
    pid_t pid= waitpid(-1, NULL, WNOHANG);//给子进程收尸
    printf("remove proc id : %d\n", pid); 
}

int main(int argc, char** argv)
{
    if(argc != 2)
    {
        fprintf(stderr,"Usage : %s <port>\n",argv[0]);
        return 1;
    }

    // 设置子进程结束信号
   struct sigaction act;
   act.sa_handler = read_childproc;//设置信号处理函数
   sigemptyset(&act.sa_mask);//置0
   act.sa_flags = 0;
   sigaction(SIGCHLD, &act, NULL);//设置信号



    // 创建socket
    int serv_sock(-1);
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock < 0)
    {
        perror("socket()");
        return 1;
    }
    // 初始化ip,port
    struct sockaddr_in serv_add;
    memset(&serv_add, 0, sizeof(serv_add));
    serv_add.sin_family = AF_INET;//IPv4协议族
    serv_add.sin_addr.s_addr = htonl(INADDR_ANY);//自动获取本机ip
    serv_add.sin_port = htons(atoi(argv[1]));

    //分配ip,port
    int ret = bind(serv_sock, (struct sockaddr*)&serv_add, sizeof(serv_add));
    if(ret < 0)
    {
        perror("bind()");
        return 1;
    }
    //进入请求等待 
    int ret0 = listen(serv_sock,5);
    if(ret0 < 0)
    {
        perror("listen()");
        return 1;
    }

    int clnt_sock(-1);//用来保存客户端套接字
    struct sockaddr_in clnt_addr;//用来保存客户端地址
    socklen_t clnt_size = sizeof(clnt_addr);

    while(true)
    {
        //接收客户端请求
        clnt_sock = accept(serv_sock,(struct sockaddr*)&clnt_addr, &clnt_size);
        if(clnt_sock < 0)
        {
            continue;
        }
        else
            puts("new client connection....");
        pid_t pid;
        pid = fork();//创建子进程
        if(pid < 0)//error
        {
            close(clnt_sock);
            continue;
        }
        else if(pid ==0)//child
        {
            close(serv_sock);//
            int str_len(0);
            char message[BUF_SIZE];
            while((str_len = read(clnt_sock,message,BUF_SIZE)) != 0)
            {
                write(clnt_sock, message, str_len);
            }
            close(clnt_sock);
            puts("client disconnected....");
            return 0;
        }
        else //父进程
        {
            close(clnt_sock);//从队列中删除

        }

    }

    close(serv_sock);
    return 0;
}

客户端

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

const int BUF_SIZE = 1024;

int main(int argc, char** argv)
{
    if(argc != 3)
    {
        printf("Usage : %s <ip> <port> \n", argv[0]);
        exit(1);
    }

    // 创建套接字
    int clt_sock(-1);
    clt_sock = socket(PF_INET, SOCK_STREAM, 0);
    if(clt_sock < 0)
    {
        perror("socket()");
        exit(1);
    }
    // 初始化服务器地址
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    // 发送请求
    int ret = connect(clt_sock,(struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if(ret < 0)
    {
        perror("connect()");
        exit(1);
    }
    else
    {
        puts("Connected.......");
    }

    char message[BUF_SIZE];

    while (true)
    {
        fputs("Input message(Q to quit): ",stdout);
        fgets(message, BUF_SIZE, stdin);

        if(!strcmp(message,"q\n") || !strcmp(message, "Q\n"))
            break;
        
        write(clt_sock, message, strlen(message));
        int str_len = read(clt_sock, message, BUF_SIZE -1);
        message[str_len] = 0;

        printf("Message from server : %s", message);

    }
    
    close(clt_sock);
    return 0;
}

2.分割I/O

在这里插入图片描述

服务器端如1一样。

客户端

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

const int BUF_SIZE = 30;

void read_routine(int sock, char* buf)
{
    while(true)
    {
        int str_len = read(sock, buf, BUF_SIZE);
        if(str_len == 0)
            return ;
        buf[str_len] = 0;
        printf("<Message from server> : %s", buf);
    }

}

void write_routine(int sock, char* buf)
{
    while(1)
    {
        fgets(buf, BUF_SIZE, stdin);
        if(!strcmp(buf, "q\n") || !strcmp(buf,"Q\n"))
        {
            shutdown(sock, SHUT_WR);//关闭输出流,但是不关闭输入流
            return;
        }

        write(sock, buf, strlen(buf));
    }
}

int main(int argc, char** argv)
{
    if(argc != 3)
    {
        printf("Usage : %s <ip> <port> \n", argv[0]);
        exit(1);
    }

    // 创建套接字
    int clt_sock(-1);
    clt_sock = socket(PF_INET, SOCK_STREAM, 0);
    if(clt_sock < 0)
    {
        perror("socket()");
        exit(1);
    }
    // 初始化服务器地址
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    // 发送请求
    int ret = connect(clt_sock,(struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if(ret < 0)
    {
        perror("connect()");
        exit(1);
    }
    else
    {
        puts("Connected.......");
    }

    char message[BUF_SIZE];

    // 创建子进程
    pid_t pid;
    pid = fork();

    if(pid < 0)// error
    {
        perror("fork()");
        return 1;
    }
    else if (pid == 0) //child
    {
        write_routine(clt_sock, message);

    }
    else
    {
        read_routine(clt_sock, message);
    }

    close(clt_sock);
    return 0;
}

六、进程间通信

1.基于管道(PIPE)的通信

#include <unistd.h>

int pipe(int pipefd[2]);

成功返回0,失败返回-1.

  • pipefd[0] : 通过管道接收数据时使用的文件描述符,即管道出口。
  • pipefd[1] :通过管道传输数据时使用的文件描述,即管道入口。
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <string.h>
#include <wait.h>

const int BUF_SIZE = 40;

int main()
{
    int fd[2];
    
    int ret = pipe(fd);
    if(ret < 0)
    {
        perror("pipe()");
        return 1;
    }

    pid_t pid;
    char message[BUF_SIZE];
    pid = fork();
    if(pid < 0)
    {
        perror("fork()");
        return 1;
    }
    else if(pid == 0)
    {
    //    sleep(4);
       int str_len = read(fd[0],message,BUF_SIZE);
       message[str_len] = 0;

       printf("child read : %s\n", message);
       close(fd[0]);
       close(fd[1]);  
    }
    else
    {
        // printf("in :\n");
        // fputs(message, stdin);
         char msg[] = "who are you ?";
        write(fd[1], msg, strlen(msg));
        
        close(fd[0]);
        close(fd[1]);

        wait(NULL);
    }
    return 0;
}

七、并发服务器

1.I/O复用

1.1 select()

该函数,移植性比较好。

#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, 
           struct timeval *timeout);

void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

struct timeval
{
  long    tv_sec;         /* seconds */
  long    tv_usec;        /* microseconds */
};
1.2.epoll()

这个函数仅仅适用于Linux,

linux的方言,不可以移植。

#include <sys/epoll.h>
/*
创建一个epoll描述符。
size 可以随便填,给个正数即可
*/
int epoll_create(int size);
int epoll_create1(int flags);

epoll_ctl()可以向指定的 epoll 上下文中加入或删除文件描述符:

#include <sys/epoll.h>
/* 管理一个epoll描述符 */
int epoll_ctl(int epfd, int op, int fd, 
              struct epoll_event *event);

op 的选项

名称描述
EPOLL_CTL_ADD把fd指定的文件添加到epfd指定的epoll实例监听集中
EPOLL_CTL_DEL把fd指定的文件从epfd指定的epoll监听集中删掉
EPOLL_CTL_MOD使用event改变在已有fd上的监听行为
typedef union epoll_data
{
      void        *ptr;
      int          fd;
      uint32_t     u32;
      uint64_t     u64;
} epoll_data_t;

struct epoll_event 
{ 
   uint32_t     events;      /* Epoll events */
   epoll_data_t data;        /* User data variable */
};

events参数

名称描述
EPOLLERR文件出错。即使没有设置,这个事件也是被监听的
EPOLLET在监听文件上开启边沿触发,默认行为是水平触发
EPOLLHUP文件被挂起,即使没有设置,这个事件也是被监听的
EPOLLIN文件未阻塞,可读
EPOLLONESHOT在一次事件产生并处理后,文件不再被监听。(必须指定新事件)
EPOLLOUT文件未阻塞,可写
EPOLLPRI高优先级的带外数据可读
#include <sys/epoll.h>

/* wait for an I/O event on an epoll file descriptor */
int epoll_wait(int epfd, struct epoll_event *events,
               int maxevents, int timeout);

如果 timeout 为0,即使没有事件发生,调用也立即返回,此时调用返回0。如果 timeout 为 -1,调用将一直等待到有事件发生。
当调用返回,epoll_event 结构体中的 events 字段描述了发生的事件。 data字段包含了所有用户在调用 epoll_ctl()前的设置。

1.2.2 案例
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <cstring>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/epoll.h>


const int BUF_SIZE = 100;

int main(int argc, char** argv)
{
    if(argc != 2)
    {
        printf("Usage %s <port> \n",argv[0]);
        return 1;
    }
    int sock_serv;

    // 创建套接字
    sock_serv = socket(PF_INET, SOCK_STREAM, 0);
    if(sock_serv < 0)
    {
        perror("socket()");
        return 1;
    }
    // ip,port
    struct sockaddr_in serv_add;
    memset(&serv_add, 0, sizeof(serv_add));
    serv_add.sin_family = AF_INET;
    serv_add.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_add.sin_port = htons(atoi(argv[1]));

    int ret = bind(sock_serv,(struct sockaddr*)&serv_add, sizeof(serv_add));
    if(ret < 0)
    {
        perror("bind()");
        return 1;
    }

    int ret1 = listen(sock_serv, 5);
    if(ret1 < 0)
    {
        perror("listen()");
        return 1;
    }

    int sock_clnt;
    // struct timeval mytime;
    // fd_set readset;
    // FD_ZERO(&readset);
    // 把服务器套接字放入监视集合中
    // FD_SET(sock_serv,&readset);
    // int fd_max = sock_serv;
    // fd_set copy_readset;
    struct sockaddr_in clnt_addr;

    // 创建epoll
    int epfd = epoll_create(5);
    // 设置事件
    struct epoll_event* epevent;//保存返回的事件
    struct epoll_event event;//保存一开始的事件
    event.events = EPOLLIN;
    event.data.fd = sock_serv;

    // 给epoll添加描述符和事件
    epoll_ctl(epfd, EPOLL_CTL_ADD, sock_serv, &event);

    //初始化
    epevent =(struct epoll_event*) malloc(sizeof(epevent)*5);

    char message[BUF_SIZE];
    while(true)
    {
        // copy_readset = readset;
        // mytime.tv_sec = 5;
        // mytime.tv_usec = 5000;
        // int ans = select(fd_max + 1, &copy_readset, NULL, NULL, &mytime);
        int epoll_count = epoll_wait(epfd, epevent, 5, -1);
        if(epoll_count < 0)
        {
            perror("epoll_wait()");
            break;
        }
        else if(epoll_count == 0)
        {
            // printf("time over \n");
            continue;
        }
        else 
        {
            for(int i = 0; i < epoll_count; ++i)
            {
                // if(FD_ISSET(i, &copy_readset))
                if(epevent[i].data.fd == sock_serv)
                {
                    // if(i == sock_serv)//sock_serv有请求
                    // {
                        socklen_t clnt_size = sizeof(clnt_addr);
                        sock_clnt = accept(sock_serv, (struct sockaddr*)&sock_clnt,&clnt_size);
                        if(sock_clnt < 0)
                            {
                                continue;
                            }
        
                        // FD_SET(sock_clnt,&readset);
                        // if(fd_max < sock_clnt)
                        // {
                        //     fd_max = sock_clnt;
                        // }
                        struct epoll_event clnt_event;
                        clnt_event.data.fd = sock_clnt;
                        clnt_event.events = EPOLLIN;
                        epoll_ctl(epfd, EPOLL_CTL_ADD, sock_clnt, &clnt_event);

                        printf("Client %d is connecting...\n", sock_clnt);

                }
                else
                {
                        // int str_len = read(i, message, BUF_SIZE);
                    int str_len = read(epevent[i].data.fd, message, BUF_SIZE);
                    if(str_len == 0) //关闭请求
                    {
                            // FD_CLR(i, &copy_readset);
                         epoll_ctl(epfd, EPOLL_CTL_DEL, sock_clnt,NULL);
                        close(i);
                        printf("close client: %d \n", i);
                    }
                    else
                    {
                        write(epevent[i].data.fd, message, str_len);
                    }
                }
            }

        }
                 
    }
    close(epfd);
    close(sock_serv);   
    return 0;
}

1.2.3边沿触发与条件触发

条件触发方式中,只要输入缓冲有数据就会一直通知该事件。

边缘触发中输入缓冲收到数据时仅注册1次该事件。即使输入缓冲中还留有数据,也不会再注册。边缘触发方式下,以阻塞方式工作的read和write函数有可能引起服务器端的长时间停顿。因此,边缘触发方式中一定要采用非阻塞read和write函数。

将套接字改为非阻塞方式的方法

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );
名称含义
F_GETFL获得fd的文件描述符属性
F_SETFL更改文件描述符属性

将文件改为非阻塞模式

int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag | O_NONBLOCK);

2.多进程实现并发

看五、多进程服务

3.多线程服务器端

3.1 创建线程
#include <pthread.h>
/*
pthread_t* 传入一个该类型的地址
pthread_attr_t 设置线程的属性
第三个参数:传一个函数,返回类型为void*,参数为void*,
第四个参数:为函数传入参数
*/
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

//Compile and link with -pthread.
3.2 等待线程的消亡
#include <pthread.h>

/*
thread : 线程号
retval : 保存线程函数的返回值。
*/
int pthread_join(pthread_t thread, void **retval);
3. 3 互斥量
#include <pthread.h>

pthread_mutex_t;

int pthread_mutex_destroy(pthread_mutex_t *mutex);

//第二个参数为锁的属性,可以为NULL
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
              const pthread_mutexattr_t *restrict attr);
#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
3.4 信号量
#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

int sem_destroy(sem_t *sem);
//Link with -pthread.
  • sem : 创建信号量时传递保存信号量的变量地址值。
  • pshared :创建可由多个进程共享的信号量,传递0,创建只允许1个进程内部使用的信号量。
  • value :指定新创建的信号量初始值。
int sem_post(sem_t *sem);

int sem_wait(sem_t *sem);

int sem_trywait(sem_t *sem);

使用模板

sem_wait(&sem);
//临界区的开始
.....
//临界区的结束
sem_post(&se)

八、多种I/O函数

1.send(),recv()

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

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

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

flags

名称含义
MSG_OOB用于传输带外数据(发送紧急消息)
MSG_PEEK验证输入缓冲中是否存在接收的数据
MSG_DONTROUTE数据传输过程中不参照路由表在本地网络中寻找目的地
MSG_DONTWAIT调用I/O函数时不堵塞,用于非堵塞I/O
MSG_WITALL防止函数返回,直到接收全部请求的字节数

2.readv(),writev()

也就是说,通过writev函数可以将分散保存在多个缓冲中的数据一并发送,通过readv函数可以由多个缓冲分别接收。因此,适当使用这2个函数可以减少I/0函数的调用次数。

#include <sys/uio.h>

ssize_t readv(int fd, const struct iovec *iov, 
              int iovcnt);

ssize_t writev(int fd, const struct iovec *iov,
               int iovcnt);

struct iovec 
{
    void  *iov_base;    /* Starting address */
    size_t iov_len;     /* Number of bytes to transfer */
};

九、多播和广播

是基于UDP套接字实现。

1.设置TTL

在这里插入图片描述

2.加入多播组

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.广播

在这里插入图片描述

十、Linux编程

1.标准IO与系统IO之间的相互转化

#include <stdio.h>

//由fd转化为FILE指针类型
FILE *fdopen(int fd, const char *mode);

//由 FILE指针类型转化为fd
int fileno(FILE *stream)

2.复制文件描述符dup

#include <unistd.h>

int dup(int oldfd);
int dup2(int oldfd, int newfd);

3.实现半关闭

fd转化为FILE类型指针图。

在这里插入图片描述

通过复制文件描述符,实现半关闭。

在这里插入图片描述

在这里插入图片描述

调用shutdown,发送EOF,实现半关闭。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2209613.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《机器学习与数据挖掘综合实践》实训课程教学解决方案

一、引言 随着信息技术的飞速发展&#xff0c;人工智能已成为推动社会进步的重要力量。作为人工智能的核心技术之一&#xff0c;机器学习与数据挖掘在各行各业的应用日益广泛。本方案旨在通过系统的理论教学、丰富的实践案例和先进的实训平台&#xff0c;帮助学生掌握机器学习…

基于YOLO11深度学习的非机动车驾驶员头盔检测系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战、目标检测、卷积神经网络

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

对秋季新款上衣的数据分析

秋季新款上衣评论分析 1.评论的基本统计分析(数据来源&#xff1a;淘宝评论信息接口) 接口链接&#xff1a;https://easydoc.net/s/42414529&#xff08;有需要调用接口的可以联系博主&#xff09; 评论长度分布图&#xff1a; 根据接口拉取数据获得的评论数据&#xff0c;并…

使用Ubuntu开发Zephyr RTOS时遇到FATAL ERROR: required program JLinkExe not found...解决办法

硬件平台&#xff1a;STM32L432RCT6 zephyr版本&#xff1a;Zephyr version 3.7.99 开发环境&#xff1a;ubuntu 24.4 在Ubuntu环境下遇到FATAL ERROR: required program JLinkExe not found; install it or add its location to PATH错误&#xff0c;意味着系统找不到JLinkEx…

Linux系统的用户和用户群组的各种权限总结

Linux系统的用户和用户群组的各种权限总结 用户群组用户群组文件添加群组&#xff1a;groupadd 用户用户文件新建用户&#xff1a;useradd修改用户&#xff1a;usermod删除用户&#xff1a;userdel 文件归属&#xff1a;chown文件权限&#xff1a;chmod相关文件和目录 用户群组…

网络通信——流量与路由(万字解读)

前言:流量控制与路由更新控制 如何控制网络流量可达性? 方案一:可通过修改路由条目(即对接收和发布的路由进行过滤)来控制流量可达性,这种方式称为路由策略。 方案二:可直接通过依据用户制定的策略进行转发,且该策略优于路由表转发,这种方式称为策略路由。 (1)什么…

音视频入门基础:H.264专题(18)——AVCDecoderConfigurationRecord简介

一、引言 H.264流行的包装方式有两种&#xff0c;一种是AnnexB&#xff0c;另一种是avcC。对于AnnexB包装的H.264码流&#xff0c;其SPS和PPS被当做普通的NALU来处理&#xff1b;而对于avcC包装的H.264码流&#xff0c;其SPS和PPS信息存贮在AVCDecoderConfigurationRecord中&a…

Java项目实战II基于Java+Spring Boot+MySQL的服装销售平台(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 在当今数字…

C++面试速通宝典——24

452. Linux进程地址空间 文本段&#xff1a;包含程序的可执行代码初始化数据段&#xff08;数据段&#xff09;&#xff1a;包含已初始化的全局变量和静态变量。未初始化数据段&#xff08;BSS段&#xff09;&#xff1a;包含未初始化的全局变量和静态变量。堆&#xff1a;动态…

Python 在Excel中添加数据条

在Excel中添加数据条是一种数据可视化技巧&#xff0c;它通过条形图的形式在单元格内直观展示数值的大小&#xff0c;尤其适合比较同一列或行中各个单元格的数值。这种表示方式可以让大量的数字信息一目了然。本文将介绍如何使用Python在Excel中的指定单元格区域添加数据条。 …

Apache HTTP Server 配置SSL证书(Windows)

Apache2.4.39 HTTP Server 配置SSL证书 1. 申请证书 申请证书步骤(略) 证书名称如下: ca-bundle.crt xxx_com.crt xxx_com.key2. 配置 证书位置:Apache2.4.39\conf\ssl Apache2.4.39\conf\ssl修改两个配置文件: 0localhost_80.conf <VirtualHost *:80>Rewrite…

【HTML格式PPT离线到本地浏览】

文章目录 概要实现细节小结 概要 最近在上课时总是出现网络不稳定导致的PPT无法浏览的情况出现&#xff0c;就想到下载到电脑上。但是PPT是一个HTML的网页&#xff0c;无法通过保存网页&#xff08;右键另存为mhtml只能保存当前页&#xff09;的形式全部下载下来&#xff0c;试…

数据库实验2-2

10-1 将学号为“1911203”的学生的联系电话改为“590987” 本题目要求编写SQL语句&#xff0c; 在students表中&#xff0c;将学号为“1911203”的学生的联系电话改为“590987”。 提示&#xff1a;请使用UPDATE语句作答。 表结构: 请在这里写定义表结构的SQL语句。例如&am…

碳钎维:无人机轻量化关键材料!

一、碳纤维材料特性 轻质高强&#xff1a; 碳纤维是一种含碳量在95%以上的高强度新型纤维材料&#xff0c;具有极高的比强度和比刚度。 在同等重量下&#xff0c;其拉伸强度可达到钢、铝合金、钛合金的9倍以上&#xff0c;弹性模量可以达到钢、铝合金、钛合金的4倍以上。 耐…

c语言经典100例

1.字符串转为数字 #include <stdio.h>int strToInt(char *s) {int num0;int sign1;int step1;if (*s -){sign -1;s;}while (*s > 0&&*s < 9){num num*10(*s-0);step 10;s;}return num*sign; }int main() {char a[10] "-1234";char *s a ;pr…

git删除错误的commit

文章目录 1、git删除错误的commit2、.gitignore配置文件不生效的问题 1、git删除错误的commit git的流程如图&#xff1a; 当某次失误造成commit的版本有问题&#xff0c;需要回退到正常的版本修改后重新add。 首先通过git log查看commit提交记录&#xff0c;可以看到HEAD-…

2024最新版安装教程!Python安装+PyCharm安装使用教程!!(非常简单)

Python下载安装 一、进入Python官网首页&#xff0c;下载最新版的Python 官方网址&#xff1a;Download Python | Python.org 鼠标悬浮在Downloads&#xff0c;选择最新版本 注意&#xff1a;由于Python官网服务器设立在国外&#xff0c;所以下载速度非常慢&#xff0c;我这…

在centos(ubuntu)中如何通过预构建二进制文件安装nodejs

首先去Node.js下载你说需要的版本的预构建二进制文件Node.js — 下载 Node.js 在CentOs或Ubuntu离线服务器上安装Node.js&#xff0c;你可以通过下载Node.js的预构建二进制文件来完成。以下是具体步骤&#xff1a; 获取Node.js预构建二进制文件&#xff1a; 在有网络连接的机器…

【Linux系统编程】线程的简单运用

目录 前言&#xff1a; 一&#xff0c;线程退出 二&#xff0c;进程异常 三&#xff0c;资源共享 四&#xff0c;单线程的代码运用 五&#xff0c;多线程的代码运用 六&#xff0c;线程的优缺点 七&#xff0c;线程的私有与共享 前言&#xff1a; 学习有关线程的代码运…

论文笔记:Prompt-Based Meta-Learning For Few-shot Text Classification

论文来源&#xff1a;EMNLP 2022 论文地址&#xff1a;2022.emnlp-main.87.pdf (aclanthology.org) 代码地址&#xff1a;GitHub - MGHZHANG/PBML GB/T 7714 Zhang H, Zhang X, Huang H, et al. Prompt-Based Meta-Learning For Few-shot Text Classification[C]//Proceedi…