Linux客户端与服务端的通信,内附流程和源代码

文章结构

  • 客服端通信流程
  • 服务端通信流程
  • 展示程序中其他函数说明
  • 客服端完整代码展示
  • 服务端完整代码展示
  • 运行说明
  • 运行截图
  • 建议:要熟悉通信流程,倒背如流的那种;
  • 源代码我也会上传到CSDN,可自行下载;

客服端通信流程

  1. 创建套接字sockfd,socket流;
  2. 向服务端发起链接;
  3. 数据交互,接收或发送数据;
  4. 关闭socket;
过程相关函数:

1.1. 创建套接字函数

int socket(int domain,int type,int protocol);
domain:
   AF_INET;//一般情况下只用到这个参数,使用TCP或UDP传输,用IPv4地址
type:
   SOCK_STREAM;//一般情况下也只用这个参数
   //含义:这个协议是按照顺序的、可靠的、数据完整的基于字节流的传输;
protocol:
   //一般填0,表示默认
//该函数失败返回0,成功则返回socket的文件描述符

1.2. 链接服务端函数

int connect(int sockfd,const struct sockaddr* addr, socklen_t addrlen);
sockfd:
   socket文件描述符;
addr:
   指定服务端信息,包括IP地址和端口号;
addrlen:
   指定addr的大小
返回值:
   成功返回0,失败返回-1

1.3. 数据交互

//发送数据
ssize_t send(int sockfd,const void *buf,size_t len,int flags);
sockfd:
   socket函数返回值;
*buf:
   接收数据缓冲区地址;
len:
   缓冲区大小;
flags:
   一般填0;
返回值:失败返回-1,成功返回的值比较复杂,现在不必关心;

//接收数据
ssize_t recv(int sockfd,void *buf,size_t len,int flags);
sockfd:
   socket函数返回值;
*buf:
   接受数据的缓冲区;
len:
   缓冲区大小;
flag:0

1.4. 关闭套接字

调用close()即可

服务端通信流程

  1. 创建套接字sockfd;
  2. 绑定通信的端口号和IP;
  3. 设置socket为监听模式;
  4. 接受客服端链接:
  5. 数据交互;
  6. 关闭套接字;
过程相关函数

1.1 与客服端的socket()函数一样;
1.2 绑定通信的端口号和IP函数

int bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
sockfd:
   socket()函数返回的值;
addr:
   要绑定的IP和端口号信息;
addrlen:
   addr的长度;
返回值:
   成功返回0,失败返回-1

1.3 设置sockfd为监听模式模式

int listen(int sockfd,int backlog);
sockfd:
   socket()函数返回的值;
backlog:
   排队建立3次握手队列和刚刚建立3次握手队列的连接数和;

1.4 接收客户端链接函数

int accept(int sockfd,struct sockaddr* addr,socklen_t * addrlen);
sockfd:
   socket()函数返回的值;
*addr:
   客户端地址信息;
addrlen:
   addr的大小

1.5 数据交互函数和客户端的相同;
1.6 同客户端

展示程序中其他函数说明
//sockaddr数据结构
struct sockaddr
{
   sa_family_t sa_family;//地址结构类型
   char sa_data[4];
}

struct sockaddr_in
{
   __kernel_sa_family_t sin_family;//地址结构类型
   __be16 sin_port;//端口号
   struct in_addr sin_addr;//IP地址
};

struct in_addr
{
   __be32 s_addr;
};

//IP地址转换函数
int inet_pton(int af,const char* src,void * dst);
//af结构类型,src待转换的地址,转换目标

//因为TCP/IP协议规定,网络数据流应采用大端字节序,所以有了下列函数
//网络字节序和主机字节序的转换
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint32_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
//具体含义自己可以查查

//字符串比较函数
strcmp()相等返回-1bzero()memset()函数都可以初始化结构体;
//具体自行搜索

服务端完整代码

说明:
  • 我将这些函数封装成了一个类,方便管理;
  • 实际运用中需要检查是否产生错误;
/*
1.创建套接字,socket
2.绑定通信端口号和IP地址,bind
3.设置为监听模式,listen
4.接受客服端链接,accept
5.接收/发送数据
6.关闭套接字
*/
#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>

using namespace std;

#define SERVER_PORT 6666

class TcpServer
{
public:
	TcpServer();
	~TcpServer();
	bool initSocket();
	bool bindIP();
	bool listenModel();
	bool acceptClient();
	bool SendRecvData();

private:
	int m_servaddr;
	int m_clientfd;

	struct sockaddr_in m_clientInfo;
};

int main()
{
	TcpServer Server;
	Server.initSocket();
	Server.bindIP();
	Server.listenModel();
	Server.acceptClient();

	while (1)
	{
		if (Server.SendRecvData())
			break;
	}

	return 0;
}

TcpServer::TcpServer()
{
	m_servaddr = 0;
	m_clientfd = 0;
}

TcpServer::~TcpServer()
{
	if (m_servaddr != 0)close(m_servaddr);
	if (m_clientfd != 0)close(m_clientfd);
}

bool TcpServer::initSocket()
{
	m_servaddr = socket(AF_INET, SOCK_STREAM, 0);
	if (m_servaddr == -1)
	{
		cout << "socket fail..." << endl;
		return false;
	}
	cout << "socket create successed..." << endl;

	return false;
}

bool TcpServer::bindIP()
{
	struct sockaddr_in sockfd;
	sockfd.sin_family = AF_INET;
	sockfd.sin_addr.s_addr = htonl(INADDR_ANY);
	sockfd.sin_port = htons(SERVER_PORT);
	if ((bind(m_servaddr, (struct sockaddr*)&sockfd, sizeof(sockfd))) == -1)
	{
		cout << "bind IP and Port fail..." << endl;
		close(m_servaddr);
		return false;
	}

	cout << "bind IP and Port successed..." << endl;
	return true;
}

bool TcpServer::listenModel()
{
	if ((listen(m_servaddr, 5)) == -1)
	{
		cout << "listen model fail..." << endl;
		close(m_servaddr);
		return false;
	}
	cout << "listen model set successed..." << endl;
	return true;
}

bool TcpServer::acceptClient()
{
	socklen_t clientLen = sizeof(m_clientInfo);
	if ((m_clientfd = accept(m_servaddr, (struct sockaddr*)&m_clientInfo, &clientLen)) == -1)
	{
		cout << "accept function error..." << endl;
		close(m_servaddr);
		return false;
	}
	cout << "accept function set successed..." << endl;

	return true;
}

bool TcpServer::SendRecvData()
{
	char buffer[1024];
	char str[INET_ADDRSTRLEN];
	bzero(&buffer, sizeof(buffer));
	recv(m_clientfd, buffer, sizeof(buffer), 0);
	if ((strcmp(buffer, "exit")) == 0)
		return true;
	cout << "receive from " << inet_ntop(AF_INET, (struct sockaddr*)&m_clientInfo.sin_addr, str, sizeof(str))
		<< " at PORT " << ntohs(m_clientInfo.sin_port) << endl;
	cout << "information such as: " << buffer << endl;
	return false;
}

客户端完整代码

说明:
  • 我将这些函数封装成了一个类,方便管理;
  • 实际运用中需要检查是否产生错误;
/*
1.创建socket套接字,socket函数
2.请求链接服务端,connect函数
3.数据交互
4.关闭socket套接字
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>//bzero函数
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/shm.h>

using namespace std;

#define SERVER_PORT 6666

class TcpClient
{
public:
	TcpClient();
	~TcpClient();
	bool createSocket();
	bool connectServer();
	bool SendRecvData();
private:
	int m_sockfd;
};

int main(int argc, char* argv[])
{
	TcpClient client;
	
	if (client.createSocket() == false)
		return -1;
	sleep(1);
	if (client.connectServer() == false)
		return -1;

	while (1)
	{
		if (client.SendRecvData() == true)
			break;
	}
	client.~TcpClient();

	return 0;
}

TcpClient::TcpClient()
{
	m_sockfd = 0;
}

TcpClient::~TcpClient()
{
	if (m_sockfd != 0)close(m_sockfd);
}

bool TcpClient::createSocket()
{
	m_sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (m_sockfd == -1)
	{
		cout << "create socket fail..." << endl;
		return false;
	}
	cout << "create socket successed..." << endl;
	return true;
}

bool TcpClient::connectServer()
{
	struct sockaddr_in servaddr;
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERVER_PORT);
	//htons 将一个无符号整形数从主机字节顺序转换为网络字节顺序
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	int tip = connect(m_sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
	if (tip == -1)
	{
		printf("connect fail...\n");
		close(m_sockfd);
		return false;
	}
	return true;
}

bool TcpClient::SendRecvData()
{
	char buffer[1024];
	bzero(&buffer, sizeof(buffer));
	cin >> buffer;
	send(m_sockfd, buffer, strlen(buffer), 0);
	if (strcmp(buffer, "exit") == 0)
		return true;
	return false;
}

Linux程序运行方式:
  • Linux环境下,找到相应目录执行命令:
  • g++ chatClient.cpp -o ./Client
  • g++ chatServer.cpp -o ./Server
  • 注意:chatClient.cpp和chatServer.cpp是我创建文件的文件名;
  • 然后先让服务端跑起来,否则客户端会报错,输入 ./Server;
  • 然后输入 ./Client,让客户端跑起来就能进行数据交互了;
运行截图

在这里插入图片描述
在这里插入图片描述

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值