Linux网络编程:通过socket编程实现两个服务器之间的信息自由收发(类似微信聊天)

目录


一、对accept的参数重新定义

    接着上篇建立好socket通道之后,这一次来实现让服务端和客户端能够进行一些简单的信息交互。上篇连接通道的代码如下:

        接下来开始对连接进服务器的客户端发送一些字符,并且显示打印出连接进来的客户端IP地址及端口号。

        首先获取listen监听后accept选出建立连接队列当中完成三次握手的客户端,获取该客户端的IP和端口号accept剩下的两个参数不能为NULL,第二个参数要使用struct aockaddr_in(不使用struct sockaddr是因为sockaddr_in好用)的结构体从而获取到客户端的IP和端口号第三个参数为第二个参数的大小并且取地址,accept函数原型为:

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept的返回值为一个整形的描述符,用来向客户端发送内容。写好accept之后判断accept是否正常返回,失败返回值是-1。当有客户连接时accept成功就可以通过答应输出显示客户端的IP。打印时注意把网络格式的IP转为字符串形式,使用地址转换API

inet_ntoa(struct in_addr inaddr);

二、用read和write实现信息的发和收

        

        定义一个字符数组的buf来存放客户端发的内容,用read读取数据,write发送数据,如果接收端有乱码可能是定义的数组空间过大,可以通过定义字符串,直接发送字符串,可以避免

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
      #include <netinet/in.h>
       #include <arpa/inet.h>

int main()
{

        int s_fd;
        int c_fd;
        char readbuf[128];
        char *msg = "get msg success";
        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;
        int s_len;
        int c_len;
        s_fd = socket(AF_INET,SOCK_STREAM,0);
        s_addr.sin_family = AF_INET;

        s_addr.sin_port = htons(8888);
        inet_aton("192.168.0.197",&s_addr.sin_addr);
        bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
        listen(s_fd,10);
        s_len = sizeof(struct sockaddr_in);
        c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&s_len);//最后一个参数为结构体大小的地址
        printf("%s\n",inet_ntoa(c_addr.sin_addr));
//             ssize_t read(int fd, void *buf, size_t count);
        read(c_fd,readbuf,sizeof(readbuf));//读取readbuf中客户端发送的数据
        printf("%s",readbuf);//打印出读取到的数据
//       ssize_t write(int fd, const void *buf, size_t count);

        write(c_fd,msg,sizeof(msg));//发送msg给接收端
        return 0;
}

到这一步就已经可以通过telnet在两台设备来进行收发数据。window下可以cmd来测试,但还不能实现自由收发

 window下telnet连接成功发送一个字符后收到服务端数据,这里出现了一个问题,收到的数据不是完整的,也就是可能write可能出了问题,后来发现write计算msg大小时用了sizeof,应该改为strlen,sizeof是计算的分配的空间大小,strlen计算的是字符串从第一个字符到\0时有多少个字符数不包括\0,而msg是一个char*型是一个地址,32位下的地址数据的大小为4个字节,64位下为8个字节。

三、编写客户端部分的代码实现数据收发自由(模拟两个用户聊天)

        客户端也是第一步先创建套接字,基本步骤和服务端一致,然后通过connect连接服务器,connect的返回值用于判断连接状态没有像accept一样是个描述符,之后通过套接字的描述符来向客户端发送(write)接收(read)内容。为了方便,我把IP地址和端口号用参数形式来运行程序服务端也做同样的修改,其中参数端口号是以字符串形式传入的所以要用atoi()函数把字符串转为整数,不加会出错。

int main(int argc,char **argv )
{
        int c_fd;
        char readbuf[128];
        char *msg = "message from client";
        struct sockaddr_in c_addr;
        int nread;

        memset(&c_addr,0,sizeof(struct sockaddr_in));

        //1.socket
        c_fd = socket(AF_INET,SOCK_STREAM,0);//选择TCP协议
        if(c_fd == -1){
                perror("socket");
                exit(-1);
        }
        c_addr.sin_family = AF_INET;
        c_addr.sin_port = htons(atoi(argv[2]));//选择端口号
        inet_aton(argv[1],&c_addr.sin_addr);//字符串地址转化为网络能识别的的格式
        //2.connect
//     int connect(int sockfd, const struct sockaddr *addr,
     //              socklen_t addrlen);

        if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr))){//连接服务端
                perror("connect");
                exit(-1);
        }

        //3.send
        write(c_fd,msg,strlen(msg));//发送字符串
        //4.read
        nread = read(c_fd,readbuf,128);//读取服务端发送的数据保存到缓存
        if(nread == -1){
                perror("read");
        }else{
        printf("get message from server: %d,%s\n",nread, readbuf);//输出缓存中的数据

        }
        return 0;
}

运行结果:结果正常

 四、客户端与服务端无限制聊天

        服务端和客户端在上一步位置互相发送接收一次信息就退出,想让服务端和客户端间不停的收发,且服务端还能继续保持随时接收用户连接的状态,可以通过fork()调用子进程实现,原理是服务端做一个循环一直让程序等待接收用户连接,当有用户连接时就fork()让子进程不停的做读取客户端数据的动作,同时也要能够随时的发送数据,所以需要再fork()一个子进程来单独做发送这件事。对于发送部分的子进程来说读取数据部分就是父进程,对读取部分来说accept等待接收用户部分是父进程。服务端代码如下:

#include<stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include<linux/in.h>
#include <arpa/inet.h>
//#include <sys/socket.h>
#include <stdlib.h>
#include<string.h>
#include <unistd.h>

int main(int argc,char **argv)
{
        int s_fd;
        char readbuf[128] = {0};
        char msg[128] = {0};
        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;
        int nread;

        memset(&s_addr,0,sizeof(struct sockaddr_in));
        memset(&c_addr,0,sizeof(struct sockaddr_in));

        //1.socket
        s_fd = socket(AF_INET,SOCK_STREAM,0);
        if(s_fd == -1){
                perror("socket");
                exit(-1);
        }
        s_addr.sin_family = AF_INET;
        s_addr.sin_port = htons(atoi(argv[2]));
        inet_aton(argv[1],&s_addr.sin_addr);
        //2.bind
        // int bind(int sockfd, const struct sockaddr *addr,
        //                socklen_t addrlen);

        bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
        //3.listen
        listen(s_fd,10);
        //4.accept
//      int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

        int clen = sizeof(struct sockaddr_in);
        while(1){
        int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
        if(c_fd == -1){
                perror("accept");
        }
        printf("connect\n");

        if(fork() == 0){
//      printf("get connet: %s\n",inet_ntoa(c_addr.sin_addr));
//      printf("inet_ntoa ip = %s\n",inet_ntoa(c_addr.sin_addr));
        //5.read
                if(fork() == 0){
                        while(1){
                        printf("input: \n");
                        memset(msg,0,sizeof(msg));
                        gets(msg);
                        write(c_fd,msg,strlen(msg));
                        sleep(1);
                        }
                }
        while(1){
        memset(readbuf,0,sizeof(readbuf));
        nread = read(c_fd,readbuf,128);
        if(nread == -1){
                perror("read");
        }else{
                printf("get message: %d,%s\n",nread, readbuf);
                }
        //6.write
                        }
//      break;
                }
        }
        return 0;
}

   客户端:

#include<stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include<linux/in.h>
#include <arpa/inet.h>
//#include <sys/socket.h>
#include <stdlib.h>
#include<string.h>
int main(int argc,char **argv )
{
        int c_fd;
        char readbuf[128] = {0};
        char msg[128] = {0};
        struct sockaddr_in c_addr;
        int nread;

        memset(&c_addr,0,sizeof(struct sockaddr_in));

        //1.socket
        c_fd = socket(AF_INET,SOCK_STREAM,0);
        if(c_fd == -1){
                perror("socket");
                exit(-1);
        }
        c_addr.sin_family = AF_INET; 
        c_addr.sin_port = htons(atoi(argv[2]));
        inet_aton(argv[1],&c_addr.sin_addr);
        //2.connect 
//     int connect(int sockfd, const struct sockaddr *addr,
     //              socklen_t addrlen);

        if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr))){
                perror("connect");
                exit(-1);
        }       
//      while(1){

                if(fork() == 0){
                        while(1){
                        printf("input:\n");
                        memset(msg,0,sizeof(msg));
                        gets(msg);
                        write(c_fd,msg,strlen(msg));
                        sleep(1);
                                }
                        }
                while(1){
                memset(readbuf,0,sizeof(readbuf));
                sleep(2);
                nread = read(c_fd,readbuf,128);
                if(nread == -1){
                        perror("read");
                }else{
                printf("get message from server: %d,%s\n",nread, readbuf);
                        }
                }
//      }
        //3.send
        //4.read
        return 0;
}

     补充:因为发送和接收的过程不在限制次数所以使用read之前要注意,用来放读取的缓存readbuf要在每一次read之前把readbuf的缓存memset清除,防止上一次读取的数据冗余,造成下一次读取的内容还包括上一次的读取内容。

运行结果如下:

两边可以自由的进行收发聊天就像微信聊天一样,并且不会自动退出,自由聊天功能实现。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值