提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
int socket(int domain, int type, int protocol);
1)domain:通讯协议族
PF_INET: IPV4互联网协议族
PF_INET6: IPV6互联网协议族
PF_LOCAL: 本地通信协议族
PF_PACKET: 内核底层的协议族
PF_IPX: IPX Novell协议族
2)type:数据传输类型
SOCK_STREAM: 面向连接的socket
SOCK_DGRAM: 无连接的socket
3)protocol:最终使用的协议
在IPV4互联网协议族中,传输类型为SOCK_STREAM的协议只有IPPROTO_TCP,数据传输方式为SOCK_DGRAM的协议类型只有IPPROTO_UDP
可以填0(编译器自动识别)
多个字节组成的整数存放涉及到字节序。
大端序:低位字节存在高位,高位字节存在低位(如0X123456内存中存放为12 34 56)
小端序:低位字节存在低位,高位字节存在高位(如0X123456内存中存放为56 34 12),如Intel
为了解决不同字节序的计算机之间传输数据的问题,约定采用网络字节序(大端序)
主机字节序与网络字节序之间的转换:
uint16_t htons(uint16_t hostshort);
uint32_t htonl(uint32_t hostlong);
uint16_t ntohs(uint16_t netshort);
uint32_t ntohl(uint32_t netlong);
存放协议族、端口和地址信息
struct sockaddr {
unsigned short sa_family; // 协议族
unsigned char sa_data[14]; // 14字节的端口和地址
};
sockaddr结构体为了统一地址结构的表示方法,统一接口函数,但是操作不方便,所以定义了等价的sockaddr_in结构体,其大小与sockaddr相同,可以强制转换为sockaddr
struct in_addr {
unsigned int s_addr; // IP地址,大端序
};
struct sockaddr_in {
unsigned short sa_family; // 协议族
unsigned short sin_port; // 端口号,大端序
struct in_addr sin_addr; // IP地址,32位,只适用于IPV4
unsigned char sin_zero[8]; // 未使用,为了与sockaddr大小相同
};
之所以搞两个结构体,可能是因为sockaddr可以用于IPV4,后续也可以用于IPV6,sockaddr_in是为了IPV4操作方便(根据sin_addr存放的位数看)。
typedef unsigned int in_addr_t; // 大端序IP地址
// 将字符串格式的IP转换为大端序IP
in_addr_t inet_addr(const char* cp);
int inet_aton(const char* cp, struct in_addr* inp);
// 将大端序IP转换为字符串格式IP
char *inet_ntoa(struct in_addr in);
功能需求:能够与服务端建立连接,并发送、接收三次信息
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main(int argc, char** argv)
{
if (argc < 3) {
cout << "Error: you should enter server ip and port" << endl;
cout << "Usage: ./djclient [IP ADDR] [PORT]" << endl;
return 0;
}
int client_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
struct hostent* server_ip = gethostbyname(argv[1]); // gethostname支持域名、主机名、字符串
if (server_ip == nullptr) {
cout << "gethostbyname failed " << argv[1] << endl;
close(client_socket);
return -1;
}
memcpy(&server_addr.sin_addr, server_ip->h_addr, server_ip->h_length);
int ret = connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret == -1) {
cout << "connect failed." << endl;
close(client_socket);
return -1;
}
char buf[1024] = {0};
// 接受发送消息3次
for (int i = 0; i < 3; i++) {
memset(buf, 0, sizeof(buf));
sprintf(buf, "这是发送的第%d个消息.", i);
ret = write(client_socket, buf, strlen(buf));
if (ret == -1) {
cout << "ERROR: 第 " << i << " 次发送信息失败" << endl;
break;
}
cout << "INFO: 第 " << i << " 次发送了 " << ret << " 个字节,内容为:" << buf << endl;
memset(buf, 0, sizeof(buf));
ret = read(client_socket, buf, sizeof(buf) - 1);
if (ret == -1) {
cout << "ERROR: 第 " << i << " 次接收信息失败" << endl;
break;
}
if (ret == 0) {
}
cout << "INFO: 第 " << i << " 次接收了 " << ret << " 个字节,内容为:" << buf << endl;
}
close(client_socket);
cout << "通信结束!!!" << endl;
return 0;
}
功能需求:实现简单的服务端,接收客户端连接,打印接收和连接信息
#include
#include
#include
#include
#include
#include
using namespace std;
int main(int argc, char** argv)
{
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(3560);
int ret = bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret != 0) {
cout << "ERROR: bind() 执行失败!!!" << endl;
close(server_socket);
return -1;
}
ret = listen(server_socket, 5);
if (ret != 0) {
cout << "ERROR: listen() 执行失败!!!" << endl;
close(server_socket);
return -1;
}
// int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
struct sockaddr_in cliaddr;
memset(&cliaddr, 0, sizeof(cliaddr));
socklen_t addrlen = sizeof(cliaddr);
cout << "等待连接..." << endl;
int client_socket = accept(server_socket, (struct sockaddr*)&cliaddr, &addrlen);
char* client_ip = inet_ntoa(cliaddr.sin_addr);
cout << "INFO: 来自客户端的连接为:" << client_ip << ":"
<< ntohs(cliaddr.sin_port) << endl;
char buf[1024] = {0};
int i = 0;
while (true) {
memset(buf, 0, sizeof(buf));
ret = read(client_socket, buf, sizeof(buf) - 1);
if (ret == -1) {
cout << "ERROR: 第 " << i << " 次接收信息失败" << endl;
break;
}
if (ret == 0) {
cout << "INFO: 客户端断开连接" << endl;
break;
}
cout << "INFO: 第 " << i << " 次接收了 " << ret << " 个字节,内容为:" << buf << endl;
strcpy(buf, "success!!!");
ret = write(client_socket, buf, strlen(buf));
if (ret == -1) {
cout << "ERROR: 第 " << i << " 次发送信息失败" << endl;
break;
}
cout << "INFO: 第 " << i << " 次发送了 " << ret << " 个字节,内容为:" << buf << endl;
i++;
}
close(client_socket);
close(server_socket);
return 0;
}
执行结果如下所示:
# 客户端
[root@localhost code]# g++ djclient.cpp -o djclient
[root@localhost code]# ./djclient
Error: you should enter server ip and port
Usage: ./djclient [IP ADDR] [PORT]
[root@localhost code]# ./djclient 192.168.66.124 3560
INFO: 第 0 次发送了 29 个字节,内容为:这是发送的第0个消息.
INFO: 第 0 次接收了 10 个字节,内容为:success!!!
INFO: 第 1 次发送了 29 个字节,内容为:这是发送的第1个消息.
INFO: 第 1 次接收了 10 个字节,内容为:success!!!
INFO: 第 2 次发送了 29 个字节,内容为:这是发送的第2个消息.
INFO: 第 2 次接收了 10 个字节,内容为:success!!!
通信结束!!!
[root@localhost code]#
# 服务端
[root@centos server]# g++ djserver.cpp -o djserver
[root@centos server]# ./djserver
等待连接...
INFO: 来自客户端的连接为:192.168.91.153:14098
INFO: 第 0 次接收了 29 个字节,内容为:这是发送的第0个消息.
INFO: 第 0 次发送了 10 个字节,内容为:success!!!
INFO: 第 1 次接收了 29 个字节,内容为:这是发送的第1个消息.
INFO: 第 1 次发送了 10 个字节,内容为:success!!!
INFO: 第 2 次接收了 29 个字节,内容为:这是发送的第2个消息.
INFO: 第 2 次发送了 10 个字节,内容为:success!!!
INFO: 客户端断开连接
[root@centos server]#
C++实现客户端/服务端通信(一)基于socket通信的基本API,实现了客户端/服务端的基本通信框架。