文章结构
- 客服端通信流程
- 服务端通信流程
- 展示程序中其他函数说明
- 客服端完整代码展示
- 服务端完整代码展示
- 运行说明
- 运行截图
- 建议:要熟悉通信流程,倒背如流的那种;
- 源代码我也会上传到CSDN,可自行下载;
客服端通信流程
- 创建套接字sockfd,socket流;
- 向服务端发起链接;
- 数据交互,接收或发送数据;
- 关闭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()即可
服务端通信流程
- 创建套接字sockfd;
- 绑定通信的端口号和IP;
- 设置socket为监听模式;
- 接受客服端链接:
- 数据交互;
- 关闭套接字;
过程相关函数
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()相等返回-1;
bzero()和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,让客户端跑起来就能进行数据交互了;
运行截图