回声服务器端实现

126 阅读3分钟

回声服务器端实现

1. 创建虚拟机

可以用Xshell或者FinalShell连接虚拟机方便操作

2. 查看虚拟机IP地址

ip addr 
​
这个IP地址可能会变,是动态获取的
  • image-20220512200917703.png

3. 提权

sudo su -
  • image-20220512200929152.png
  • image-20220512200959017.png

4. 编写服务器端的代码

- cd /home  # 到根目录下的home目录中
    - 可以多用pwd来查询当前所在的目录
    - ls查看目录包含的文件
- mkdir proSer # 创建一个总的项目文件夹(PS:若是对命令行不习惯可以用Xftp对虚拟机上的文件进行管理)
- cd proSer # 进入到proSer目录中
- mkdir echo_server # 创建echo_server文件夹
- cd echo_server/ # 进入到这个目录
- vim echo_server.c # vim创建这个echo_server.c文件(vim命令没有就安装一个,或者就用vi)
- 后面代码的编写,我更倾向用IDE来开发,可以查一下vscode的插件remote explorer 来对Linux进行远程开发
- 前面说过的那些命令语句,等利用插件连接到虚拟机后,也可以在vscode的终端上操作
  • image-20220512205901005.png
  • image-20220512210221450.png

5. 如果出现VSCODE保存不了代码

可以用chmod 777 文件夹或文件名来修改权限

ll查看属性

权限分为三种:读(r=4),写(w=2),执行(x=1)。综合起来还有可读可执行(rx=541)、可读可写(rw=642)、可读可写可执行(rwx=7421)。所以755代表rwxr-xr-x当前用户可读可执行。
​
777代表rwxr-rwx-rwx所有用户都可读可写可执行。
    
chmod是Linux下设置文件权限的命令,后面的数字表示不同用户或用户组的权限。一般是三个数字:第一个数字表示文件所有者的权限第二个数字表示与文件所有者同属一个用户组的其他用户的权限第三个数字表示其它用户组的权限。  

6. sockaddr_in结构体

sin_family指代协议族,在socket编程中只能是AF_INET
sin_port存储端口号(使用网络字节顺序)
​
sin_addr存储IP地址,使用in_addr这个数据结构
​
sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节

7. 完整服务器端代码

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>// 宏定义服务器端口号 
#define SERVER_PORT 6666/** 
 *! 收信准备
 *! 1. 准备信箱
 *! 2. 准备标签
 *! 3. 标签写上地址和姓名
 *! 4. 把标签贴到信箱上
 *! 5. 挂置于小区传达室
 *///! 判断传来的字符是不是小写 
int isLowC(char c)
{
    if (c - 'a' >= 0 && c - 'a' <= 25) return 1;
    else return 0;
}
​
int main ()
{
    //! 用整型表示这个信箱sock
    //! 创建信箱 sys/socket.h
    int sock;
​
    //! 定义标签(包括端口号和IP) 在头文件中有 sys/socket.h
    //! sockaddr_in socket 地址
    //! sockaddr_in是系统封装的一个结构体,具体包含了成员变量:sin_family、sin_addr、sin_zero
    /**
     * @brief sockaddr_in结构体
     * 
     *! sin_family指代协议族,在socket编程中只能是AF_INET
     *! sin_port存储端口号(使用网络字节顺序)
     *! sin_addr存储IP地址,使用in_addr这个数据结构
     *! sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节
     */
    struct sockaddr_in server_addr;  //! 定义服务器的地址标签
​
    //! socket函数的参数1代表网络通信家族, 这里面涉及到的就是指定TCP/IP
    //! 参数2表示使用TCP协议
    //! 1. 功能:创建一个信箱
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sock) 
    {
        printf ("create socket error\n");
        return -1;
    }
    
    //! 2. 功能:接下来要先清空标签,再写上地址和端口号
    //! 参数1是地址,参数2是要清零的结构有多少字节
    bzero(&server_addr, sizeof(server_addr));
​
    //! 指定协议家族 - 因特网网络协议家族 IPV4
    server_addr.sin_family = AF_INET; 
    //! 对应的IP地址
    //! 要监听哪一个IP地址
    //! 如果要绑定本地所有的IP地址,有几个网卡就有几个IP地址
    //! INADDR_ANY(宏定义) 表示所有的IP地址
    //! htonl把IP地址的字节顺序进行调整  把机器上的字节顺序,调整为网络字节顺序
    //! host to net 
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
​
    //! 绑定端口号 要将主机字节序列改成网络
    server_addr.sin_port = htons(SERVER_PORT);
​
    //! 3. 将标签贴到收信信箱上
    //! 参数1 信箱; 参数2 struct sockaddr * 指针,强转
    //! 参数3 server_addr 内容大小
    if (-1 == bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)))
    {
        perror("bind");
        close(sock);
        return -1;
    }
​
    //! 4. 将信箱挂置在小区传达室上------即监听信号
    //! 参数2即同一时刻允许像我们的服务器端发送连接的客户端的数量
    if (-1 == listen(sock, 6)) 
    {
        printf ("listen error\n");
        return -1;
    } 
    //! 就等信件了
    printf ("等待客户端的连接\n");
​
    //! 来了信件要怎么接收
    //! 服务器要一直运行
    int done = 1;
    //! 对于读写,有一个新的客户端的套接字
    //! 定义客户端的socket 套接字
    int client_sock; //! 新
    while (done)
    {
        //! 要注意接收的信件是来自谁(accept提供了一个参数)
        struct sockaddr_in client; //! 客户端 标签 sockaddr_in
​
        
​
        char client_ip[64]; //!存储客户端IP
​
        //! 接收从客户端来的内容
        char buf[256];
        int len; //! 接收从客户端来的数据的长度
​
        socklen_t client_addr_len;
        client_addr_len = sizeof(client);
        //! 参数1 从信箱中接收信件 
        //! 参数2 接收客户端的时候也要注意强转
        //! 参数3 这个结构体的长度 client
        //! accept函数会通过参数3返回一些信息,所以参数3这里需要传递一些地址进去
        //! accept函数返回和客户端通信的socket
        //todo 和客户端交流的socket client_sock 
        //todo 从客户端读信回信通过 client_sock
        client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);
​
        //! 打印客户端地址和端口号
        //! 网络字节转换成主机字节ntop
        //! 网络字节序不是字符串,是字节 逆向的整数表达
        // if (client_sock)
        // {
        printf("client ip : %s\t port : %d\n",
                inet_ntop(AF_INET, &client.sin_addr.s_addr, client_ip, sizeof(client_ip)),
                ntohs(client.sin_port));
            // );
​
            // break;
        // }
       
        //! 5. 读取客户端发送的数据 客户端对应的socket 是 client_sock
        //! 参数1 是客户端的socket 
        //! 参数2 是接收的数据要存储在哪里 buf 但是buf这里面不是字符串 read函数是不做处理的不添加字符串结束符的'\0'
        len = read(client_sock, buf, sizeof(buf) - 1);
        buf[len] = '\0';
        printf ("receive[%d]: %s\n", len, buf);
        // if (len) break;
​
        for (int i = 0; i < len; ++i)
        {
            // if (isLowC(buf[i]))
            // {
            //     buf[i] = 'A' + buf[i] - 'a';
            // }
            //! 利用自带的函数
            buf[i] = toupper(buf[i]);
        }
        //! 接着要将收到的信息写回去 
        //! 参数1 往客户端的socket(就像一个通道) 写
        len = write(client_sock, buf, len);
        printf("write finished, len: %d\n", len);
        close(client_sock); //! 记得挂机 跟这次的客户端
    }
    close(sock);
    return 0;
}
​

8. 利用windows自带的telnet工具,模拟网络客户端的功能

运行服务器端代码后,在客户端这边利用 telnet ip 端口号命令来连接服务器端

连接成功后,按ctrl + ],接着输入send 字符串,传给服务器字符串消息

1. 演示过程如下:

(1)先启动服务器端程序

image-20220515174259588.png

(2)在本机windows系统上利用telnet工具测试

telnet IP地址 端口号

image-20220515174516714.png

(3)输入测试字符串

image-20220515174620906.png

(4)接着服务器端显示结果

image-20220515174654332.png

(5)客户端显示服务器回传的字符串(因为回收资源,所以会断开连接)

image-20220515174854013.png