Linux 网络编程(c/s模型)

网络字节序:

大端和小端的概念:

大端:地位地址存放高位数据,高位地址存放地位数据

小端:地位地址存放地位数据,高位地址存放高位数据

计算机中数据的存放方式都是以小端的形式,在网络中却是以大端的形式存放,所以在linux中有几个函数是进行存储方式的转换的

函数包含的头文件:<arpa/inet.h>

  1. htonl函数:uint32_t htonl(uint32_t hostlong);

作用:把本地的(小端)IP地址转换为网络中(大端)需要的IP地址形式

  1. htons函数:uint16_t htons(uint16_t hostshort);

作用:把本地的(小端)port端口号转换为网络中(大端)需要的port端口号形式

  1. ntohl函数:uint32_t ntohl(uint32_t netlong);

作用:把网络中的(大端)IP地址转换为本地的(小端)IP地址形式

  1. ntohs函数:uint16_t ntohs(uint16_t netshort);

作用:把网络中的(大端)port端口号转换为本地的(小端)port端口号形式

查看监听状态和连接状态的命令

netstat命令:netstat -anp | grep 8888

a表示显示所有

n表示显示的时候以数字的方式来显示

p表示显示进程的消息

  1. 简单的c/s模型

简单的c/s模型

1.函数使用:

1.socket函数:包含在头文件 <sys/types.h>或者<sys/socket.h>

➡函数原型:int socket(int domain, int type, int protocol);

作用:创建一个用于链接的套接字

2.connect函数:包含在头文件 <sys/types.h>或者<sys/socket.h>

➡函数原型:int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

作用:连接服务器的函数

3.memset函数: 包含在头文件 <string.h>

➡函数原型:void *memset(void *s, int c, size_t n);

作用:把数组中的内容置为某一个值

4.inet_pton和inet_ntop函数: 包含在头文件 <arpa/inet.h>

➡函数原型:int inet_pton(int af, const char *src, void *dst);

参数 af:指定的IP协议类型,有AF_INET和AF_INET6两个选项

参数 src:传入的IP地址(点分十进制)

参数 dst:传出的地址,转换后的网络字节序IP地址

返回值:成功返回1,出现异常返回0(也就是指向了一个无效的IP地址),失败返回-1

作用:给sockaddr_in结构体中的s_addr的值附上地址值,也是把点分十进制的字节序转换为网络的字节序

➡函数原型:const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);

参数 af:指定的IP协议类型,有AF_INET和AF_INET6两个选项

参数 src:传入的IP地址,网络字节序IP地址

参数 dst:地址转换后存放的缓冲区,本地字节序

参数 size:缓冲区大小

返回值:成功返回dst,失败返回NULL

作用:把网络字节序转换为点分十进制字节序

5.bind函数:包含在头文件 <sys/types.h>或者<sys/socket.h>

➡函数原型:int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

作用:绑定IP地址

6.listen函数:包含在头文件 <sys/types.h>或者<sys/socket.h>

➡函数原型:int listen(int sockfd, int backlog);

作用:监听客户端链接请求

7.accept函数:包含在头文件 <sys/types.h>或者<sys/socket.h>

➡函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)

作用:接受客户端的连接

服务器代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>//使用网络包含的库
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>

int main()
{
        int lfd = socket(AF_INET, SOCK_STREAM, 0);
        if(lfd < 0)
        {
                perror("soket error");
                return -1;
        }
        struct sockaddr_in serv;
        bzero(&serv, sizeof(serv));//该函数初始化serv
        serv.sin_family = AF_INET;
        serv.sin_port = htons(8888);
        serv.sin_addr.s_addr = htonl(INADDR_ANY);//表示使用本地任意可用IP地址
        int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv));
        if(ret < 0)
        {
                perror("bind error");
                return -1;
        }
        //监听
        listen(lfd, 128);
        //接受链接
        int cfd = accept(lfd, NULL, NULL);
        printf("lfd==[%d], cfd==[%d]\n", lfd, cfd);

        int n = 0;
        int i = 0;
        char buf[1024];
        while(1)
        {
                //读数据
                memset(buf, 0x00, sizeof(buf));
                n = read(cfd, buf, sizeof(buf));
                if(n <= 0)
                {
                        printf("read error or client close\n");
                        break;
                }
                printf("n==[%d], buf==[%s]\n", n, buf);

                for(i = 0;i < n;i++)
                {
                        //buf[i] = toupper(buf[i]);//该函数把小写字母转换成大写字母
                }
                //发生数据
                write(cfd, buf, n);
        }
        //关闭监听文件描述符和通信文件描述符
        close(lfd);
        close(cfd);
        return 0;
}

                                   

客户端代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>

int main()
{
        //创建socket,与服务端进行通信
        int cfd = socket(AF_INET, SOCK_STREAM, 0);
        if(cfd < 0)
        {
                perror("socket error");
                return -1;
        }
        //连接服务器
        struct sockaddr_in serv;
        serv.sin_family = AF_INET;
        serv.sin_port = htons(8888);
        inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);//赋值地址
        printf("[%x]\n", serv.sin_addr.s_addr);//输出地址
        int ret = connect(cfd, (struct sockaddr*)&serv, sizeof(serv));
        if(ret < 0)
        {
                perror("connect error");
                return -1;
        }
        int n = 0;
        char buf[256];
        while(1)
        {
                //读标准输入数据
                memset(buf, 0x00, sizeof(buf));
                n = read(STDIN_FILENO, buf, sizeof(buf));
                //发送数据
                write(cfd, buf, n);
                //读服务端发来的数据
                memset(buf, 0x00, sizeof(buf));
                n = read(cfd, buf, sizeof(buf));
                if(n <= 0)
                {
                        printf("read error or server closed\n");
                        break;
                }
                printf("n==[%d], buf==[%s]\n", n, buf);
        }
        //关闭套接字
        close(cfd);
        return 0;
}
  1. 多进程并发的c/s模型

1.使用到的函数

  1. fork函数:包含在头文件 <sys/types.h>和<unistd.h>

➡函数原型:pid_t fork(void);

作用:创建一个子进程

返回值:fork函数存在两个返回值

在父进程中,fork会返回新创建的子进程ID

在子进程中,fork会返回0

注意:在调用fork函数后,fork函数后面的所有代码会执行两遍

两次的执行流程:

如果fork执行成功,那么会先返回一个大于0的数,也就是子进程的ID,会执行父进程代码,当执行完fork函数后面的代码后,程序会再一次执行fork函数后面的代码,这时候fork返回值为0

  1. struct sigaction结构体:包含在头文件<signal.h>

结构体原型:

struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };

成员sa_handler:这是一个函数指针,指定一个信号处理函数

成员sa_mask:用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置

成员sa_falgs:用来设置信号处理的其他相关操作,下列的数值可用

可选选项:

1.SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL

2.SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用

3.SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号

  1. sigaction函数:包含在头文件<signal.h>

➡函数原型:int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

作用:检查或修改与指定信号相关联的处理动作

signum参数指出要捕获的信号类型

act参数指定新的信号处理方式,

oldact参数输出先前信号的处理方式

服务端代码

#include<stdio.h>                                                  
#include<stdlib.h>                                                  
#include<sys/socket.h>                                               
#include<arpa/inet.h>
#include<sys/wait.h>                                                  
#include<string.h>
#include<strings.h>                                                     
#include<unistd.h>                                                     
#include<errno.h>  
#include<arpa/inet.h>
#include<ctype.h>
#include<signal.h>                                                    
#include<pthread.h>

#include "wrap.h"

#define SRV_PORT 10110

void catch_child(int signum)
{
    while((waitpid(0, NULL, WNOHANG))>0);//阻塞子进程
    return;
}

int main(int argc, int *argv[])
{
    int lfd;
    struct sockaddr_in srv_addr;
    //memset(&srv_addr, 0, sizeof(srv_addr));
    bzero(&srv_addr, sizeof(srv_addr));
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_port = htons(SRV_PORT);
    srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    lfd = Socket(AF_INET, SOCK_STREAM, 0);

    Bind(lfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
    
    Listen(lfd, 128);

    struct sockaddr_in clt_addr;//客户端使用的
    socklen_t clt_addr_len = sizeof(clt_addr);//accept参数需要使用的类型
    pid_t pid;
    int cfd;
    int retf;
    while(1)
    {
        cfd = Accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len);
        //创建子进程
        pid = fork();
        if(pid < 0)//子进程创建失败
        {
            perr_exit("fork error");
        }
        else if(pid == 0){//子进程创建成功
            break;
        }
        else//处理主进程
       {
            //回收子进程
            struct sigaction act;//创建一个结构体
            act.sa_handler = catch_child;//捕捉子进程           
            sigemptyset(&act.sa_mask);//清空子进程
            act.sa_flags = 0;
            retf = sigaction(SIGCHLD, &act, NULL);
            if(retf != 0)
            {
                perr_exit("sigaction error");
            }

            close(cfd);
            continue;
        }
    }
    char buf[1024] = {0};
    int ret = 0;
    int i = 0;
    if(pid == 0)
    {
        while(1)
        {
            ret = Read(cfd,buf, sizeof(buf));
            if(ret == 0)
            {
                close(cfd);
                exit(0);
            }
            for(i = 0;i < ret; i++);
            {
                buf[i] = toupper(buf[i]);
            }
            write(cfd, buf, ret);
            write(STDOUT_FILENO, buf, ret);
        }
    } 
    return 0;
}

3.多线程并发c/s模型

1.使用的函数

1.pthread_create函数:包含在头文件<pthread.h>

➡函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

作用:创建一个子线程

参数1:事先创建好的pthread_t类型的参数。成功时tidp指向的内存单元被设置为新创建线程的线程ID

参数2:用于定制各种不同的线程属性

参数3:新创建线程从此函数开始运行

参数4:start_rtn函数的参数。无参数时设为NULL即可。有参数时输入参数的地址。当多于一个参数时应当使用结构体传

返回值:若线程创建成功返回0,否则返回出错编号

2.pthread_detach函数:包含在头文件<pthread.h>

➡函数原型:int pthread_detach(pthread_t thread);

作用: 线程分离状态,线程主动与主控线程断开关系。使用pthread_exit或者线程自动结束后,其退出状态不由其他线程获取,而直接自己自动释放

参数:传入指定的线程编号

返回值:成功返回0,失败返回错误编号

服务器代码

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>

#include "wrap.h"

#define MAXLINE 8192
#define SERV_PORT 8000

struct s_info {                     //定义一个结构体, 将地址结构跟cfd捆绑
    struct sockaddr_in cliaddr;
    int connfd;
};

void *do_work(void *arg)
{
    int n,i;
    struct s_info *ts = (struct s_info*)arg;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];      //#define INET_ADDRSTRLEN 16  可用"[+d"查看

    while (1) {
        n = Read(ts->connfd, buf, MAXLINE);                     //读客户端
        if (n == 0) {
            printf("the client %d closed...\n", ts->connfd);
            break;                                              //跳出循环,关闭cfd
        }
        printf("received from %s at PORT %d\n",
                inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
                ntohs((*ts).cliaddr.sin_port));                 //打印客户端信息(IP/PORT)

        for (i = 0; i < n; i++) 
            buf[i] = toupper(buf[i]);                           //小写-->大写

        Write(STDOUT_FILENO, buf, n);                           //写出至屏幕
        Write(ts->connfd, buf, n);                              //回写给客户端
    }
    Close(ts->connfd);

    return (void *)0;//结束该子线程
}

int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    pthread_t tid;
    struct s_info ts[256];      //根据最大线程数创建结构体数组.
    int i = 0;

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);                     //创建一个socket, 得到lfd

    bzero(&servaddr, sizeof(servaddr));                             //地址结构清零
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);                   //指定本地任意IP
    servaddr.sin_port = htons(SERV_PORT);                           //指定端口号 8000

    Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //绑定

    Listen(listenfd, 128);      //设置同一时刻链接服务器上限数

    printf("Accepting client connect ...\n");

    while (1) {
        cliaddr_len = sizeof(cliaddr);
        connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);   //阻塞监听客户端链接请求
        ts[i].cliaddr = cliaddr;
        ts[i].connfd = connfd;

        /* 达到线程最大数时,pthread_create出错处理, 增加服务器稳定性 */
        pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
        pthread_detach(tid); //线程已经创建好,需要关闭线程句柄                                                //子线程分离,防止僵线程产生.
        i++;
    }

    return 0;
}

1.setsockopt函数:包含在头文件<sys/types.h>和<sys/socket.h>

函数原型:

int setsockopt(int sockfd, int level, int optname,
                      const void *optval, socklen_t optlen);

作用:用于任意类型、任意状态套接口的设置选项值

参数含义:

sockfd:标识一个套接口的描述字;

level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6; optname:需设置的选项;

optval:指针,指向存放选项待设置的新值的缓冲区;

optlen:optval缓冲区长度;

返回值:若成功返回0,发生错误会返回-1

使用实例

int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(opt));

2.getsockopt函数:包含在头文件<sys/types.h>和<sys/socket.h>

函数原型:

int getsockopt(int sockfd, int level, int optname,
                      void *optval, socklen_t *optlen);

作用:用于获取任意类型、任意状态套接口的选项当前值,并将结果存入optval

参数含义:

sockfd:标识一个套接口的描述字;

level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP;

optname:需获取的套接口选项;

optval:指针,指向存放所获得选项值的缓冲区;

optlen:指针,指向optval缓冲区的长度值;

返回值:若成功返回0,发生错误会返回-1

4.半关闭

意思:TCP链接中A发送FIN请求关闭,B端回应ACK后,A端进入FIN_WAIT_2状态,进入半关闭状态(实际上就是只有一端关闭)

close函数和shutdown函数的区别:

close函数只能关闭一个套接字或者是一个文件描述符

shutdown函数可以仅关闭读,仅关闭写,关闭读写,情况更加精细。同时,当出现多个文件描述符指向同一socket时,调用shutdown,可以将所有的指向都断开

注意:

  1. 如果有多个进程共享一个套接字,close 每被调用一次,计数减 1,直到计数为0时,也就是所用进程都调用了 close,套接字将被释放。

  1. 在多进程中如果一个进程调用了 shutdown(sfd,SHUT RDWR)后,其它的进程将无法进行通信。但,如果一2个进程 close(sfd)将不会影响到其它进程

shutdown函数原型:包含在头文件<sys/socket.h>

int shutdown(int sockfd, int how);

参数sockfd:指定的套接字

参数how:指定的断开方式,有一下选项

  • SHUT_RD:关闭sockfd上的读功能,此选项将不允许sockfd进行读操作,该套接字不再接受数据,任何在当前套接字缓冲区的数据都会被丢弃

  • SHUT_WR:关闭sockfd上的写功能,此选项将不允许sockfd进行写操作,进程不能对此套接字进行写操作

  • SHUT_RDWR:关闭sockfd的读写功能,相当于调用两次shutdown,首先是SHUT_RD,再SHUT_WR

猜你喜欢

转载自blog.csdn.net/weixin_62859191/article/details/128822145