【二】Socket实战---服务端的实现过程

❤️ 专栏简介 :网络通信和Socket编程是LinuxC/C++服务器开发的基础。本专栏从最基础的内容开始学习网络通信和socket编程的相关内容,循序渐进的掌握网络通信的和socket编程的相关知识。主要内容包括网络通信与socket编程概述、socket通信模型、套接字概述、socket通信交互流程以及Socket通信中各个函数的实现以及功能等。

☀️ 专栏适用人群 :适用于具备基础 Linux 知识的并想从零开始学习网络通信和Socket编程初学者;以及想学习Linux上c/c++服务器开发的朋友们。

🌙专栏特点:通俗易懂、图文并茂、非常详细;

🌴 专栏说明 :如果文章知识点有错误的地方,欢迎大家随时在文章下面评论,我会第一时间改正。让我们一起学习,一起进步。

🍄 专栏地址:https://blog.csdn.net/anchenliang_1002/category_11919076.html

本节我们来通过一个实际的实例来演示socket通信;Socket通信要有服务端和客户端;本节我们先实现一个网络通信中的服务端。

我的环境:centos 7.6

一、服务端的流程

服务端的实现流程如下:

socket()-->bind( )-->listen()-->accept()-->read()/write()--->close()

socket()//创建套接字
bind()//分配套接字地址
listen()//等待连接请求状态
accept()//允许连接,类似于打电话过程中的“接听”功能。
read()/write()//进行数据交换
close()//断开连接

一、服务端源码实现

首先,我们先建立一个的目录来作为socket学习的目录,比如我在/home/smb/share/新建一个socket文件夹:

cd /home/smb/share/
mkdir socket
cd socket/

然后创建服务端源文件:

touch server.c
vim server.c

然后将以下代码复制进去

#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 666  //定义服务器端口号

int main(void)
{
    int sock;//用于存放请求消息;对应第一节写信例子中的信箱
    struct sockaddr_in server_addr;//用于存放ip和端口;对应第一节写信例子中的标签

    //创建套接字,对应于第一节写信例子中小明的“信箱”
    //AF_INET代表使用协议族IPV4;
    //SOCK_STRESAM表示使用TCP协议;
    sock = socket(AF_INET,SOCK_STREAM,0);

    //将server_addr清零;
    bzero(&server_addr,sizeof(server_addr));

    //指定协议族为AF_INET,IPV4
    server_addr.sin_family = AF_INET;

    //指定ip地址,对应第一节写信例子中的小明的地址(北京市朝阳区王府井大街10086号)
    //INSDDR_ANY宏定义代表绑定本地所有ip地址,也可理解为绑定本地所有网卡
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    //指定接收服务的端口号
    server_addr.sin_port = htons(SERVER_PORT);

    //将套接字和服务绑定;对应第一节写信的例子就是将标签贴到信箱上
    bind(sock,(struct sockaddr *)&server_addr,sizeof(server_addr));

    //监听套接字;对应第一节写信的例子就是收件人小明的邮筒等待快递员来送信;且同时能收到的信上限为128封
    listen(sock,128);

    printf("等待客户端的连接\n");

    while(1)
    {
        struct sockaddr_in client;//定义要接收的客户端,里面存放有客户端的ip等信息;对应第一节写信的例子就是写信方的地址等信息

        int client_sock;//定义客户端的socket

        char client_ip[64];//定义存放客户端ip的数组

        char buf[2048];//存放客户端发送的数据

        int  buf_len;//客户端具体发过来的消息的长度


        socklen_t client_addr_len = sizeof(client);//得到客户端消息的长度信息

        //接收客户端发过来的消息;
        client_sock = accept(sock,(struct sockaddr *)&client,&client_addr_len);

        //打印客户端ip和端口号
        printf("client ip:%s\t  port is:%d\n",
                 inet_ntop(AF_INET,&client.sin_addr.s_addr,client_ip,sizeof(client_ip)),       
                 ntohs(client.sin_port));

        //读取客户端发送的数据
        buf_len = read(client_sock,buf,sizeof(buf)-1);

        buf[buf_len] = '\0';//为buf结束的位置添加结束符

        printf("recive: buf_len is %d, buf is \n %s\n",buf_len,buf);

        buf_len = write(client_sock,buf,buf_len);

        printf("write finished, send len is %d\n",buf_len);

        close(client_sock);//关闭客户端的socket
    }

    return 0;
}

二、代码解释

第14行int socket 是定义套接字,通俗来讲,对应第一节中写信的例子,就是创建小明的信箱
第15行struct sockaddr_in server_addr;表示定义server_addr用于存放ip和端口;对应第一节写信例子中的标签
第20行 sock = socket(AF_INET,SOCK_STRESAM,0);表示创建套接字了;对应于第一节写信例子中小明的“信箱”;其中AF_INET代表使用协议族IPV4;SOCK_STRESAM表示使用TCP协议;
第26行server_addr.sin_family = AF_INET;表示指定协议族为AF_INET,即IPV4
第30行server_addr.sin_addr.s_addr = hton1(INADDR_ANY);表示指定IP地址;对应第一节写信例子中的小明的地址(北京市朝阳区王府井大街10086号);INSDDR_ANY宏定义代表绑定本地所有ip地址,也可理解为绑定本地所有网卡
第33行server_addr.sin_port = htons(SERVER_PORT);表示指定端口;

想一想第一节中写信的例子:通过第14行和第20行的代码,我们写好了“信箱”,通过15、26、30、33行代码我们写好了“标签”,下一步是不是该把标签贴到收信的信箱上了呢?

就要用到bind函数:

第36行bind(sock,(struct sockaddr *)&server_addr,sizeof(server_addr))表示将套接字和服务绑定;对应第一节写信的例子就是将标签贴到信箱上。bind函数的第一个参数是套接字,即信箱,第二个参数是要绑定服务的指针,这里需要强转成sockaddr 结构体类型。第三个参数是第二个参数结构体的大小。

那现在贴好标签的信箱也有了,是不是应该把信箱挂到小区传达室,就可以等待快递员给咱们的信箱里送信了呢?就要用得到listen函数:

第39行中listen(sock,128);表示监听sock套接字的消息;对应第一节写信的例子就是收件人小明的邮筒等待快递员来送信;且同时能收到的信上限为128封。

到这里,套接字建立好了,服务也建立了,监听套接字也完成了,就等来消息了;对应第一节的例子就是:贴好标签的信箱有了,信箱也挂到小区门口了,所以就等着快递员来送信了;

对于socket来说,消息来了是需要接收的;对应第一节中收信的例子不太恰当,我们可以理解为打电话过程中,被打电话的一方,需要接听接电话;accept就是“接听”;而且呢,我们还得需要知道这封信是谁发过来的,即发信方的姓名地址等信息;这里使用accept函数接收,当accept之后,就相当于打电话过程中对方接听了,当然,客户端和服务端就建立了连接,就可以一直说话了。

第45行struct sockaddr_in client;定义了要接受消息的客户端,即使哪个客户端发过来的请求;里面面存放有客户端的ip等信息;对应第一节写信的例子就是写信方的地址等信息。

第59行client_sock = accept(sock,(struct sockaddr *)&client,&client_addr_len);就是在使用accept函数接收客户端发过来的消息。其中第一个参数是使用的socket套接字,即对应写信的例子就是“信箱”;第二个参数是表示接收哪个客户端发来的消息;第三个参数是长度;关于这些函数的详细讲解后面再学习,现在简单了解一下即可。

然后最后我们可以打印一下客户端的ip和端口号。

三、启动服务端并测试

写完后在刚刚的目录下编译一下,并启动服务端:

gcc server.c -o server
./server

如下:

在这里插入图片描述

就表示成功了。

测试一下:

我们在浏览器中输入我们服务器ip:666,比如我的ip是10.41.62.126,则在浏览器中输入10.41.62.126:666,如下图所示:
在这里插入图片描述

虽然页面上什么都没出来,但是看我们的后台页面,可以看到已经打印出了客户端的ip和端口了,如下图所示:

在这里插入图片描述
到这里我们简单的服务端就成功实现了,且已经能接收到客户端的连接了;我这里因为访问了两次,所以会有两条记录。

四、查看收到的客户端消息内容

现在我们成功收到了客户端的数据;我们来看一下客户端到底发了什么消息过来,使用read函数:

buf_len = read(client_sock,buf,sizeof(buf)-1);read函数第一个参数是客户端的sock;第二个参数是存放客户端消息的buf;第三个参数读取的数据长度,sizeof(buf)其实是2048,这是前面设置的,那为什么要-1呢,因为后面我们要在buf后加上\0结尾符,假设如果咱们读2048,正好来了2048个字符,再在后面加\0结尾符,这不就2047个字符了嘛,而咱们的buf定义的是2048,所以就造成了内存越界;所以咱们读取的长度为izeof(buf)-1,即2047个,最后留一个字符给\0

然后编译,重新运行server,并在浏览器中输入ip:666,看我们后台结果如下:

在这里插入图片描述

红框中就是打印出来的此次收到的客户端发来的消息的内容;具体内容是什么含义,咱们以后会学到。

五、将收到的客户端消息返回给客户端

那能不能把收到的消息再返回给客户端呢?这个当然可以,利用write函数:
len_buf = write(clent_sock,buf,len_buf);,write的第一个参数是使用的socket,第二个参数是发送的内容,第三个参数是内容的长度。

然后编译,重新运行server,并在浏览器中输入ip:666,看我们后台结果如下:
在这里插入图片描述
可以看到我们发送了1412个长度的消息,但是为什么没在浏览器中显示出来呢,这个下面再说,这里可以证明咱们确实是发出去了即可。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乘凉~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值