LinuxC 网络 -- TCP(C/S)demo

同一时间只能响应一个客户端的请求,将小写字母转换成大写字母

服务器端代码如下:

#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>

#define SERVPORT 8080

int main()
{
    // 地址结构

    /*
    struct sockaddr    //通用地址 ---- 这个结构体里没有端口号
    {
        u_short sa_family;   ---- Sa_family 地址族,采用“AF_xxx”的形式,如:AF_INET
        char sa_data[14];    ---- Sa_data 14字节的特定协议地址
                             ---- "192.168.1.10:55555" --- 要做字符串解析
    };
    */
    // u_short ---- 无符号短整数

    /*
    struct sockaddr_in
    {
        short sin_family;   ---- Internet 地址族
        unsigned short int sin_port;    ---- 端口号
        struct in_addr sin_addr;    ---- ip地址
        unsigned char sin_zero[8];    ---- 填0
    };

    struct in_addr
    {
        unsigned long s_addr;
    };
    */    //    ----    S_addr 32位的地址

    struct sockaddr_in servaddr, cliaddr;
    int listenfd, connfd;
    socklen_t clilen;
    char mesg[1024];
    int bytes_recv;
    int i;

    listenfd = socket(AF_INET, SOCK_STREAM, 0); // socket(套接字)接口实现,socket 一种文件描述符
                                                // SOCK_STREAM 流式套接字 ---- 可靠的面向连接的,TCP协议
    // SOCK_DGRAM 数据报套接字 ---- 一种无连接的,数据通过相互独立的报文进行传输,UDP协议
    // 原始套接字(很少用) ---- 用在测试新的网络协议
    /*
        int socket(int family,int type,int protocol)
        socket() 打开一个网络通讯端口,成功返回文件描述符,出错返回-1

        ipv4 ---- family ---- AF_INET
        TCP ---- tpye ---- SOCK_STREAM        UDP ---- tpye ---- SOCK_DGRAM
        protocol (协议) ---- 指定为 0
    */
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 32字节
    //servaddr.sin_addr.s_addr = inet_addr("192.168.1.105");  //inet_addr ---- 不用字节序转换
   
    /*
    int inet_aton(const char *cp,struct in_addr* inp);
    char *inet_ntoa(struct in_addr in);
    */

    // INADDR_ANY ---- 表示本机的任意ip地址
    //                 (服务器可能存在多个网卡,每个网卡可能绑定多个ip地址)
    //                 (这样设置可以在所有的ip地址上监听,直到某个客户端建立了连接时才确定到底用哪个ip地址)

    // 字节序转换 #include<arpa/inet.h>
    /*
        uint32_t htonl(uint32_t hostlong); 把 unsigned long 类型从主机序转换到网络序
        uint16_t htons(uint16_t hostlong); 把 unsigned short 类型从主机序转换到网络序
        uint32_t ntohl(uint32_t netlong);  把 unsigned long 类型从网络序转换到主机序
        uint32_t ntohl(uint16_t netshort); 把 unsigned short 类型从网络序转换到主机序

        h --- host (主机)
        n --- network
        l --- 32位长整数
        s --- 16位短整数
        如果主机是小端字节序,函数将参数做相应的大小端转换后返回
        如果主机是大端字节序,函数不做转换,参数原封不动的返回
    */

    servaddr.sin_port = htons(SERVPORT); // SERVPORT ---- 端口号

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
    /*
    int bind(int sockfd,const struct sockaddr* myaddr,socklen_t addrlen);
    bind() 将参数 socket 和 myaddr 绑定在一起,
    使sockfd 监听 myaddr 所描述的地址和端口号
    */

    listen(listenfd, 1024); // linux:#include<sys/listen.h>
    
    /*
    等待别人连接

    listen在套接字函数中表示让一个套接字处于监听到来的连接请求的状态
    int listen(int fd, int backlog);

    sockfd 一个已绑定未被连接的套接字描述符
    backlog 连接请求队列的最大长度

    listen函数使用主动连接套接字变为被连接套接口
    使得一个进程可以接受其它进程的请求,从而成为一个服务器进程
    在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
    listen函数一般在调用bind之后-调用accept之前调用。
    */
    
    for (;;) //等价与 while(1)
    {
        clilen = sizeof(cliaddr);
        connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
        /*
        accept 连接 ---- 实现三次握手 
        int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen)

        返回值:连接成功,返回文件描述符 连接文件描述符
        实现收发就用accept的返回值

        三方握手完成后,服务器调用accept()接受连接
        如果服务器调用accept() 还没有客户端的连接请求,就会阻塞等待客户机连接
        cliaddr 传出参数 返回时,传出客户端的地址和端口号
        addrlen 传入传出参数,传入是调用 所提供的缓冲区cliaddr的长度以避免缓冲区溢出问题
                            传出是客户端地址结构体的实际长度(可能没有占满调用者提供的缓冲区)
        给cliaddr参数传NULL,表示不关心客户端的地址
        */

        while (1)
        {
            bytes_recv = recvfrom(connfd, mesg, 1024, 0, (struct sockaddr *)&cliaddr, &clilen);
            /*
            recvfrom函数(经sockfd接收数据)
            函数原型:
            ssize_t recvfrom(int sockfd,void *buf,size_t len,unsigned int flags, struct sockaddr *from,socklen_t *fromlen);
            ssize_t ---- long int
            socklen_t ---- int

            sockfd:标识一个已连接套接口的描述字。
            buf:接收数据缓冲区。
            len:缓冲区长度。
            flags:调用操作方式。是以下一个或者多个标志的组合体,可通过“ | ”操作符连在一起,MSG_DONTWAIT:操作不会被阻塞。

            MSG_PEEK:指示数据接收后,在接收队列中保留原数据,不将其删除,随后的读操作还可以接收相同的数据。
            MSG_TRUNC:返回封包的实际长度,即使它比所提供的缓冲区更长, 只对packet套接字有效。
            MSG_WAITALL:要求阻塞操作,直到请求得到完整的满足。然而,如果捕捉到信号,错误或者连接断开发生,或者下次被接收的数据类型不同,仍会返回少于请求量的数据。
            MSG_EOR:指示记录的结束,返回的数据完成一个记录。
            MSG_TRUNC:指明数据报尾部数据已被丢弃,因为它比所提供的缓冲区需要更多的空间。
            MSG_CTRUNC:指明由于缓冲区空间不足,一些控制数据已被丢弃。
            MSG_OOB:指示接收到out-of-band数据(即需要优先处理的数据)。
            MSG_ERRQUEUE:指示除了来自套接字错误队列的错误外,没有接收到其它数据。

            from:(可选)指针,指向装有源地址的缓冲区。
            fromlen:(可选)指针,指向from缓冲区长度值。

            如果正确接收返回接收到的字节数,失败返回-1.
            */
            if (0 == bytes_recv)
            {
                printf("client is offline!\n");
                break;
            }

            mesg[bytes_recv] = '\0';

            printf("revc ok\n");

            for (i = 0; i < bytes_recv; i++)
            {
                mesg[i] = toupper(mesg[i]);
                /*  //#include <ctype.h>
                int toupper(int c);
                将字符c转换为大写英文字母
                如果 c 有相对应的大写字母,则该函数返回 c 的大写字母,
                否则 c 保持不变。返回值是一个可被隐式转换为 char 类型的 int 值
                */
            }

            sendto(connfd, mesg, bytes_recv, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
            /*
            ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
            只有short int flout > 1个字节的数据,需要字节序转换
            */
        }
        close(connfd);  //shutdown --- 可以根据要求断开连接
    }

    close(listenfd);

    return 0;
}

客户端代码:

#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>

#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>

#define SERVPORT 8080

int main(int argc, char const *argv[])
{
    int sockfd;

    struct sockaddr_in servaddr;
    char send_line[1024];
    char recv_line[1024];
    int bytes_recv;

    if (argc != 2)
    {
        printf("need server IP!\n");
        exit(0);
    }

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERVPORT);
    servaddr.sin_addr.s_addr = inet_addr(argv[1]);  //"inet_aton ---- 需要字节序转换"
                               //inet_addr ---- 不用字节序转换

    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    /*
    int connect(int sockfd,const struct sockaddr* servaddr,socklen_t addrlen);
    客户机需要调用connect()连接服务器
    connect() 与 bind() 的参数形式一致
    bind() ---- 自己的地址
    connect() ---- 对方的地址
    返回值:成功 0    错误 -1
    */

    while (fgets(send_line, 1024, stdin) != NULL)
    //stdin是标准输入流,可以理解为键盘
    //stdout是标准输出流,可以理解是屏幕
    
    /*
    char *fgets(char *str, int n, FILE *stream);
    str  ----   这是指向一个字符数组的指针,该数组存储了要读取的字符串。
    n  ----   这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
    stream  ----   这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。

    如果成功,该函数返回相同的 str 参数。
    如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。
    如果发生错误,返回一个空指针。

    在读字符时遇到end-of-file,则eof指示器被设置,
    如果还没读入任何字符就遇到这种情况,则stream保持原来的内容,返回NULL;
    如果发生读入错误,error指示器被设置,返回NULL,stream的值可能被改变。
    */
    {

        sendto(sockfd, send_line, strlen(send_line), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));

        bytes_recv = recvfrom(sockfd, recv_line, 100, 0, NULL, NULL);

        recv_line[bytes_recv] = '\0';

        printf("result is : %s\n", recv_line);
    
    }

    close(sockfd);

    return 0;
}

运行结果如下

在中间客户端发送数据时候会发生阻塞,无法响应其他客户端

 中间客户端下线之后,右端收到消息

 全部下线之后:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值