❤️ 专栏简介 :网络通信和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个长度的消息,但是为什么没在浏览器中显示出来呢,这个下面再说,这里可以证明咱们确实是发出去了即可。