Linux网络编程——实现双方聊天

服务器建立socket,能够等待客户端的连接,读取发来的信息并发送回应
代码实现如下: 
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc,char *argv[])
{
    int s_fd;
    int c_fd;
    int n_read;
    char readbuf[128];
    char msg[128] = {0};

    struct sockaddr_in s_addr;
    struct sockaddr_in c_addr;

    //每次清空之后再配置
    memset(&s_addr,0,sizeof(struct sockaddr_in));
    memset(&c_addr,0,sizeof(struct sockaddr_in));

    // socket()  创建一个socket,该socket与本地的 8989 端口绑定,并阻塞式等待接收客户端的连接
    s_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(s_fd == -1)
    {
        perror("socket error");
        exit(1);
    }else{
        printf("socket success\n");
    }

    //配置socket  设置本地要绑定的IP地址和端口号,通过传参的形式来设置
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(atoi(argv[2]));
    inet_aton(argv[1],(struct in_addr *)&s_addr.sin_addr);

    // bind()  
    int ret = bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
    if(ret == -1){
        perror("bind error\n");
        exit(1);
    }else{
        printf("bind success\n");
    }

    // listen()  通知内核可以连接10个客户端
    listen(s_fd,10);

    int clen = sizeof(struct sockaddr_in);

    while(1)
    {
        // accept()  连接到已完成TCP握手的客户端
        c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
        if(c_fd == -1)
        {
            perror("accept");
        }else{
            printf("get connect\n");
        }

        //打印成功连接的消息,并且获取连接上来的客户端IP
        printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));

        //客户端接入之后,创建进程不断读取客户端发来的消息
        // 父进程负责读取消息
        if(fork() == 0)
        {
            //子进程负责写入消息
            if(fork() == 0)
            {
                while(1)
                {
                    memset(msg,0,sizeof(msg));
                    printf("input:\n");
                    scanf("%s",msg);
                    // write()
                    write(c_fd,msg,strlen(msg));
                }
            }
            while(1)
            {
                memset(readbuf,0,sizeof(readbuf));
                //read()  等待读取对方发来的信息,如果接收不到消息会一直阻塞在这边
                n_read = read(c_fd,readbuf,128); 
                if(n_read == -1)
                {
                    perror("read error");
                    exit(1);
                }else{
                    printf("get the message form client:%s\n",readbuf);
                }
            }
        }
   }
   return 0;
}

 

客户端建立socket,连接服务器,并不断与其进行消息对话
代码实现如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc,char *argv[])
{
    int c_fd;
    int n_read;
    char readbuf[128];
    char msg[128] = {0};

    struct sockaddr_in c_addr;

    //每次清空之后再配置
    memset(&c_addr,0,sizeof(struct sockaddr_in));

    // socket()  创建一个socket,该socket与本地的 8989 端口绑定,并阻塞式等待接收客户端的连接
    c_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(c_fd == -1)
    {
        perror("socket error");
        exit(1);
    }else{
        printf("socket success\n");
    }

    //配置socket  设置本地要绑定的IP地址和端口号
    c_addr.sin_family = AF_INET;
    c_addr.sin_port = htons(atoi(argv[2]));
    inet_aton(argv[1],(struct in_addr *)&c_addr.sin_addr);

    //connect()
    connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in));

    //打印成功连接的消息,并且获取连接上来的客户端IP
    printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
    
    while(1)
    {
        //fork() 创建父进程负责接收服务器端返回的消息
        if(fork() == 0)
        {
            //子进程负责向服务器端发送消息
            if(fork() == 0)
            {
                while(1)
                {
                    memset(msg,0,sizeof(msg));
                    printf("please input message to server:");
                    scanf("%s",msg);
                    // write()
                    write(c_fd,msg,strlen(msg));
                }
            }            
        }

       while(1)
       {
            memset(readbuf,0,sizeof(readbuf));
            // read() 不断读取服务器端返回的信息
            n_read = read(c_fd,readbuf,128); 
            if(n_read == -1)
            {
                perror("read error");
                exit(1);
            }else{
                printf("get message from server,readbuf:%s\n",readbuf);
            }
       }
    }
   
   return 0;
}
怎么理解socket
为什么要使用socket?

目前很多开发都不是基于单机了,跑在一台Linux系统里面的协调工作使用进程间通信,但是多机通信就不够用了,要进行外部的数据收发,得通过网络编程来实现。

进程间通信的方式:管道、消息队列、共享内存、信号量、信号——它们的特点是依赖于内核,存在缺陷——可能无法进行多机通信。

socket的使用流程

服务器:socket->bind->listen->accept->read->write->close
客户端:socket                    ->connect->write->read->close

服务器进程运行起来之后,创建socket,该socket与本地的端口进行捆绑,会阻塞式等待客户端的连接;

客户端创建本地socket,同样捆绑到本地端口,通过指定服务器的IP地址和端口号进行连接;

服务器接收客户端的请求,解除阻塞,返回已连接的socket描述符。

一个服务器通常仅创建一个监听socket,它在该服务器的生命周期内一直存在。

为什么知道IP地址和端口号很重要?

因为网络编程依赖地址和数据。

  • 地址包括IP地址和端口号。

举例:有一台IP地址指向的电脑上跑很多服务,有不同的进程,如ftp\http\socket,此时客户端以socket方式接入,到底跟哪个服务器对接要通过端口号来确定。

  • 端口号的作用

一台拥有IP地址的主机可以提供Web、FTP、SMTP等服务,那么IP地址和网络服务的关系是一对多的,主机不能单单靠IP地址来区分不同的网络服务。

端口提供一种访问通道,主机可以通过“IP地址+端口号”来区分不同的服务。

  • 数据包括协议

socket中主要了解TCP 面向连接,可靠,做精细控制的时候使用,不容易出错

UDP 面向报文,不可靠,内存响应快、数据量大,像视频这种可以丢失一些数据,人眼观测不到、影响不大可以使用。

  • 字节序

小端字节序:低序字节存储在起始地址,计算机内存中的存储顺序,如X86系列CPU

大端字节序:高序字节存储在起始地址,网络字节序

拓展思考——如何在多方消息收发时,明确光标所在的客户端位置?
代码实现如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc,char *argv[])
{
    int s_fd;
    int c_fd;
    int n_read;
    int mark = 0;
    char readbuf[128];
    char msg[128] = {0};

    struct sockaddr_in s_addr;
    struct sockaddr_in c_addr;

    //每次清空之后再配置
    memset(&s_addr,0,sizeof(struct sockaddr_in));
    memset(&c_addr,0,sizeof(struct sockaddr_in));

    // socket()  创建一个socket,该socket与本地的 8989 端口绑定,并阻塞式等待接收客户端的连接
    s_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(s_fd == -1)
    {
        perror("socket error");
        exit(1);
    }else{
        printf("socket success\n");
    }

    //配置socket  设置本地要绑定的IP地址和端口号,通过传参的形式来设置
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(atoi(argv[2]));
    inet_aton(argv[1],(struct in_addr *)&s_addr.sin_addr);

    // bind()  
    int ret = bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
    if(ret == -1){
        perror("bind error\n");
        exit(1);
    }else{
        printf("bind success\n");
    }

    // listen()  通知内核可以连接10个客户端
    listen(s_fd,10);

    int clen = sizeof(struct sockaddr_in);

    while(1)
    {
        // accept()  连接到已完成TCP握手的客户端
        c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
        if(c_fd == -1)
        {
            perror("accept");
        }else{
            printf("get connect\n");
        }

        mark++;

        //打印成功连接的消息,并且获取连接上来的客户端IP
        printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));

        //客户端接入之后,创建进程不断读取客户端发来的消息
        // 父进程负责读取消息
        if(fork() == 0)
        {
            //子进程负责写入消息
            if(fork() == 0)
            {
                while(1)
                {
                    //明确当前是与哪个客户端进行通信,且此处是自动回复给客户端
                    sprintf(msg,"hello client%d\n",mark);
                    // write()
                    write(c_fd,msg,strlen(msg));
                }
            }
            while(1)
            {
                memset(readbuf,0,sizeof(readbuf));
                //read()  等待读取对方发来的信息,如果接收不到消息会一直阻塞在这边
                n_read = read(c_fd,readbuf,128); 
                if(n_read == -1)
                {
                    perror("read error");
                    exit(1);
                }else{
                    printf("get the message form client:%s\n",readbuf);
                }
            }
        }
   }
   return 0;
}
sprintf的使用
  • int sprintf(char *str, const char *format, ...)
  • 将一个格式化的字符串输出到目的字符串中
  • 而printf是将字符串输出到屏幕上
标志位的使用

每当有新的客户端输入时,都通过标志位来添加序号。每个客户端都有自己的编号,服务器在发送消息时,就知道在与哪个客户端聊天了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值