0X00 什么是Socket通信
我们知道,在网络编程中,socket通信是入门级编程。就像是你学一门语言,就先用这门语言写出Hello World
。所以我们今天就先写出这Hello World
。
Socket通信,也就是我们所说的套接字
,这是进程间的一种通信方式,但与其它通信方式不一样的是,套接字可以通过网络进行传输。
我们知道的是,Socket通信是一种通信。通信通信,总该有两个对象才能进行相互的通信,才能叫通信。而在Socket通信中,这两端就是:Server(服务器)
和 Client(客户端)
所以要实现socket通信,就是要解决这两端的编码
0X10 怎么进行Socket通信
我们刚才说了,我们需要对两端进行编码。因此,要知道怎么通信,就得分开来讲
1. Server端
对于服务器端,我们需要进行一下三部操作
第一步,先对Socket进行初始化,定义相应的协议与类型:
listenfd = socket(AF_INET, SOCK_STREAM, 0);
第二步,绑定响应的IP与Port端口:
bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr))
第三步,监听:
listen(listenfd, maxsize)
通过这三步,相当于服务器就开始工作了,端口有数据来时,就可以捕捉,这样我们就得到了来自客户端的数据。
这就是Server端的工作
2. Client端
客户端相对于服务器端就简单一点
首先也是对Socket进行初始化,也就是创建一个套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0)
然后输入服务器的IP地址,再连接(connet),就可以进行相应的通信了:
connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr))
0X20 类的设计
我们这里是面向对象的编程,因为,我们需要设计相对应的类。
在这里我们很明确,只要设计两个类即可:Server
以及 Client
1. Server 类
首先,我们需要想清楚,server类中需要什么?
私有成员:
我们刚才看到了前面的代码中有listenfd
这个变量,他是在创建套接字是返回的值,因为在Linux中万物皆文件,因此套接字也是一个文件。
因此在调用socket()函数数调用成功后,会返回一个标识这个套接字的文件描述符,失败的时候返回-1(关于socket函数,可以看这里)
因此我们需要一个 listenfd
作为私有成员变量
还有后面我们需要用到的一个connfd
变量,他是accept
函数调用成功后的返回值,和之前listenfd
一样,也是一个文件描述符。
要进行socket通信,还有就是一个很重要的变量就是 sockaddr_in
类型的变量,sockaddr_in
是用来处理网络通信的地址
我们还需要一个 char* buff 来存储通信的信息,作为缓存区
当然,还需要port来存储我们用来进行通信的端口,以及存储最大连接数的 maxsize
私有成员差不多就这样了
公共成员:
公共成员中更多的是向外界提供的接口,也就是函数。
通过以上的分析,我们知道服务器最关键的三步,因此我们需要封装这三步操作,分别是:Socket
() Bind()
Listen()
然后我们再用一个start()
函数来封装这三种操作
现在我们只是建立好了连接,准备通信,因此我们需要一个Handle()
函数,其中封装了我们需要的操作
最后,需要关闭连接,也就是end()
函数
好了,分析就差不多了,下面是完整的代码:
server.h
#ifndef SERVER_H
#define SERVER_H
#include "unified.h"
class Server
{
private:
int listenfd;
int connfd;
struct sockaddr_in servaddr;
char* buff;
int port;
int maxsize;
void Error(const char*);
public:
Server(int Po=8000,int Size=50):port(Po),maxsize(Size){buff = new char[MAXLINE];};
void Socket();
void Bind();
void Listen();
void Handle();
void start();
void stop(){close(listenfd);};
~Server(){delete [] buff;}
};
#endif
2. Client 类
知道了上面Server类的设计之后,Client的设计就差不多啦,这里就直接给代码了:
client.h
#ifndef CLIENT_H
#define CLIENT_H
#include "unified.h"
class Client
{
private:
int sockfd;
char* sendline; //发送的内容
char* ip;
int port;
struct sockaddr_in servaddr;
void Error(const char*);
public:
Client(char* IP,int Po=8000):ip(IP),port(Po)
{
sendline = new char [MAXLINE];
}
void Socket(); //建立socket
void Connet(); //连接
void handle(); //处理
void start(); //启动
void stop(){close(sockfd);}
~Client()
{
delete [] sendline;
}
};
#endif
0X30 类的实现
可能到了现在,大家还是比较懵的,因为只有类的定义,没有实现,还是不知道怎么做
现在我们看这些关键的实现,为了看起来简洁,因此直接在代码上注释了
——————————分———————————————割———————————————线————————
server.cc
#include "server.h"
void Server::Error(const char* message)
{
printf(message,strerror(errno),errno);
exit(0);
}
void Server::Socket()
{
if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ) //初始化套接字,AF_INET表示协议族,SOCK_STREAM是采用TCP协议
Error("create socket error: %s(errno: %d)\n");// 返回值==-1,报错
memset(&servaddr, 0, sizeof(servaddr));//将servaddr结构体的内容置0
servaddr.sin_family = AF_INET;//规定协议族为SOCK_STREAM
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//这里表示的是需要监听的IP地址,也就是接受哪些IP发过来的数据包,INADDR_ANY任意,即0.0.0.0 如果是本地通信,那么就是127.0.0.1
servaddr.sin_port = htons(port); //绑定端口
}
void Server::Bind()
{
if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) //参数分别是之前的文件描述符,网络通信地址及其大小
Error("bind socket error: %s(errno: %d)\n");
}
void Server::Listen()
{
if( listen(listenfd, maxsize) == -1)//文件描述符以及最大连接数
Error("listen socket error: %s(errno: %d)\n");
}
void Server::Handle()
{
printf("======waiting for client's request======\n");
while(1){
if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){ //accept用来接受端口发来的数据,connfd文件描述符
printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
continue;
}
int n = recv(connfd, buff, MAXLINE, 0); //将接收到的数据放入buff中
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
close(connfd);
}
}
void Server::start()
{
Socket();
Bind();
Listen();
}
——————————分———————————————割———————————————线————————
client.cc
#include "client.h"
void Client::Error(const char* message)
{
printf(message,strerror(errno),errno);
exit(0);
}
void Client::Socket()
{
if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) //初始化套接字,AF_INET表示协议族,SOCK_STREAM是采用TCP协议
Error("create socket error: %s(errno: %d)\n");// 返回值小于0,报错
memset(&servaddr, 0, sizeof(servaddr)); //将servaddr结构体的内容置0
servaddr.sin_family = AF_INET; //规定协议族为SOCK_STREAM
servaddr.sin_port = htons(port);//绑定端口
}
void Client::Connet()
{
printf("ip:%s\n",ip);
if( inet_pton(AF_INET, ip, &servaddr.sin_addr) <= 0) //ip转化函数,将ip转化后赋值给servaddr.sin_addr
{
printf("inet_pton error for %s\n",ip);
exit(0);
}
printf("port:%d\n",port);
if(connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) //用connet函数进行连接
Error("connect error: %s(errno: %d)\n");
}
void Client::handle()
{
printf("send msg to server: \n");
fgets(sendline, MAXLINE, stdin);//从屏幕输入
if( send(sockfd, sendline, strlen(sendline), 0) < 0) //使用send发送
Error("send msg error: %s(errno: %d)\n");
}
void Client::start()
{
Socket();
Connet();
}
0X40 Demo
类设计好了,我们现在用一个Demo来测试一下:
Server.cc
#include "server.h"
int main(int argc, char** argv)
{
Server* ser=new Server();
ser->start();
ser->Handle();
ser->stop();
return 0;
}
Client.cc
#include "client.h"
int main(int argc, char** argv)
{
if( argc != 2){
printf("usage: ./client <ipaddress>\n");
return 0;
}
Client* cli= new Client(argv[1]);
cli->start();
cli->handle();
cli->stop();
return 0;
}
makefile
all:server client clean
server:Server.o server.o
g++ -g -o server Server.o server.o
client:Client.o client.o
g++ -g -o client Client.o client.o
Server.o:Server.cc
g++ -g -c Server.cc
Client.o:Client.cc
g++ -g -c Client.cc
server.o:server.cc
g++ -g -c server.cc
client.o:client.cc
g++ -g -c client.cc
clean:
rm server.o
rm Server.o
rm client.o
rm Client.o
使用:
make一下
先使用./server
来运行服务器端(可以放在本地,也可以放在服务器)
在使用./![client](https://img-blog.csdnimg.cn/20200310231537335.png) <ip address>
运行客户端,其中后面的是ip地址,本地请填写127.0.0.1
运行client之后会提示你输入,我们输入Hello World
之后就会在服务器端显示:
0X50 后言
好啦,这个最基本的面向对象的socket通信就完成啦
工程放 github 啦,希望大家多多支持~