C++基础--建立socket连接
在这里我建立一个最基本的TCP/IP连接,只传输最基础的数据,不考虑复用IO。
客户端
首先,命令行的参数是这样的
./client ip 5005
同时在文件内也要限定命令行参数
if(argc!=3)
{
cout<<"Using: ./client ip 5005\n";
return 0;
}
在这里IP就是你要连接的服务器IP地址
前置条件已说完,可以开始了
第一步
通过socket函数创建socket
int sockfd =socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sockfd==-1)
{
perror("socket");return -1;
}
我以前写过关于socket函数各个参数的介绍
第二步
向服务器发起连接请求
我们需要服务器的协议族、端口、IP来作为连接请求的参数。幸好有结构体可以帮忙。
struct sockaddr_in {
short int sin_family; // 协议族,通常为 AF_INET
unsigned short int sin_port; // 端口号(网络字节顺序)
struct in_addr sin_addr; // IP地址
unsigned char sin_zero[8]; // 填充字段,使结构体大小与 sockaddr 一致
};
填充字段没有内容,那是为了今后扩充写的,可是现在的ipv6好像也没用上哈哈。
第一个字段就填服务端的协议族,这里我是AF_INET
sin_family =AF_INET;
第二个字段填写端口号,端口号需要是大端序,很多系统是小端序,所以需要转换
不懂的可以在百度上学习查找,几分钟就可以了。
转换的函数是htons(),这是原型
uint16_t htons(uint16_t hostshort);
这里我们用命令行的参数就行了
sin_port = htons(atoi(argv[2]));
补充一下atoi()是处理C风格的字符串 ,stoi()是处理C++风格的
第三个字段如果你有IP地址的话使用htonl() 函数就行了,如果是域名就需要使用gethostbyname() 函数
如果使用htonl()函数只需要这样写
sin_addr.s_addr=htonl(argv[1);
如果使用gethostbyname()函数就需要结构体来保存从域名转换来的IP地址
hostent *gethostbyname(const char *name)
函数返回的是一个结构体
struct hostent {
char *h_name; // 官方主机名
char **h_aliases; // 主机的别名列表
int h_addrtype; // 地址类型(例如,AF_INET)
int h_length; // 地址长度(例如,IPv4的长度为4)
char **h_addr_list; // 网络地址列表(每个地址的长度由h_length确定)
};
这里我们得到结构体后复制到sockaddr_in 也就是连接的结构体中
struct hostent* h; //存放服务器端IP的结构体
if((h=gethostbyname(argv[1]))==nullptr) //将域名解析为IP地址
{
cout<<"gethostbyname failed()\n";
close(sockfd);
return -1;
}
memcpy(&cliaddr.sin_addr,h->h_addr,h->h_length);
第四步 就是连接了,连接需要使用函数connect()
- int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen)
连接时需要的结构体与我们连接的结构体不同,需要强制类型转换,函数的第三个参数是确定第二个参数的大小
if(connect(sockfd,(struct sockaddr *)&cliaddr,sizeof(cliaddr))==-1) //发起连接请求
{
perror("connect");close(sockfd);return -1;
}
第三步
使用send()和recv()函数传送和接收数据
这里去连接自己的服务器端
while(true)
{
memset(buffer,0,sizeof(buffer));
snprintf(buffer,1024,"这是第%d个消息.\n",tmp++);
if(send(sockfd,buffer,sizeof(buffer),0)<=0)
{
perror("send");close(sockfd);return -1;
}
memset(buffer,0,sizeof(buffer));
if((iret=recv(sockfd,buffer,sizeof(buffer),0))<=0)
{
cout<<"iret="<<iret<<endl;
}
cout<<"接收"<<buffer<<endl;
sleep(2);
}
服务端
服务端在命令行我只指定了打开的端口号,客户端连接执行需要访问这个端口,然后自动分配在其他端口传送和接收数据
if(argc!=2)
{
cout<<"Using: ./server 5005\n";
return -1;
}
第一步
这里只需要获得一个socket
int listenfd =socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(listenfd==-1)
{
perror("socket");return -1;
}
第二步
我们需要确定连接来的客户端IP等,与客户端的第二步很相像
struct sockaddr_in servaddr;
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(atoi(argv[1]));
servaddr.sin_addr.s_addr=htonl(INADDR_ANY); //将32位主机字节顺序(Host Byte Order)的整数转换为网络字节顺序
这里说一下INADDR_ANY宏是 0.0.0.0的意思,代表接收所有IP地址的连接
第三步
绑定服务端的IP和端口
if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr))==-1)
{
perror("bind");close(listenfd);return -1;
}
第四步
将socket设置为监听状态
if(listen(listenfd,5)==-1) //5表示挂起连接请求的最大队列长度
{
perror("listen");close(listenfd);return -1;
}
通过listen()函数将socket设置为监听状态
int listen(int sockfd, int backlog);
其中的backlog表示挂起连接请求的最大队列数
第五步
受理连接
int sockfd=accept(listenfd,0,0);
if(sockfd==-1)
{
perror("accept");close(listenfd);return -1;
}
accept()的函数原型
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
其中的第二个参数用于存储客户端的地址信息,不需要存储可以填NULL,第三个参数确定第二个参数的长度
第六步
建立一个循环传送和传输内容
while(true)
{
memset(buffer,0,sizeof(buffer));
if((iret=recv(sockfd,buffer,sizeof(buffer),0))<=0)
{
cout<<"iret="<<iret<<endl;close(listenfd);return -1;
}
cout<<"接收="<<buffer<<endl;
strcpy(buffer,"ok!\n");
if(iret=send(sockfd,buffer,sizeof(buffer),0)<=0)
{
cout<<"iret="<<iret<<endl;close(listenfd);return -1;
}
}
附上源码
客户端
#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<unistd.h>
using namespace std;
int main (int argc,char *argv[])
{
if(argc!=3)
{
cout<<"Using: ./client ip 5005\n";
return 0;
}
//第一步,创建客户端的socket
int sockfd =socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sockfd==-1)
{
perror("socket");return -1;
}
//第二步,向服务器发起连接请求
// struct sockaddr_in {
// short int sin_family; // 协议族,通常为 AF_INET
// unsigned short int sin_port; // 端口号(网络字节顺序)
// struct in_addr sin_addr; // IP地址
// unsigned char sin_zero[8]; // 填充字段,使结构体大小与 sockaddr 一致
// };
struct sockaddr_in cliaddr; //存放协议、端口和IP地址的结构体
memset(&cliaddr,0,sizeof(cliaddr));
cliaddr.sin_family =AF_INET;
cliaddr.sin_port = htons(atoi(argv[2])); //16位主机字节顺序(Host Byte Order)的整数转换为网络字节顺序
// struct hostent {
// char *h_name; // 官方主机名
// char **h_aliases; // 主机的别名列表
// int h_addrtype; // 地址类型(例如,AF_INET)
// int h_length; // 地址长度(例如,IPv4的长度为4)
// char **h_addr_list; // 网络地址列表(每个地址的长度由h_length确定)
// };
//其中有宏定义为 #define h_addr h_addr_list[0]
struct hostent* h; //存放服务器端IP的结构体
if((h=gethostbyname(argv[1]))==nullptr) //将域名解析为IP地址
{
cout<<"gethostbyname failed()\n";
close(sockfd);
return -1;
}
memcpy(&cliaddr.sin_addr,h->h_addr,h->h_length);
if(connect(sockfd,(struct sockaddr *)&cliaddr,sizeof(cliaddr))==-1) //发起连接请求
{
perror("connect");close(sockfd);return -1;
}
char buffer[1024];
int tmp = 1;
int iret;
while(true)
{
memset(buffer,0,sizeof(buffer));
snprintf(buffer,1024,"这是第%d个消息.\n",tmp++);
if(send(sockfd,buffer,sizeof(buffer),0)<=0)
{
perror("send");close(sockfd);return -1;
}
memset(buffer,0,sizeof(buffer));
if((iret=recv(sockfd,buffer,sizeof(buffer),0))<=0)
{
cout<<"iret="<<iret<<endl;
}
cout<<"接收"<<buffer<<endl;
sleep(2);
}
close(sockfd);
return 0;
}
服务端
//typedef unsigned int in_addr_t 存放32位大端序的IP地址
//把字符串格式的IP转换成大端序的IP,转换后的IP直接填充到sockaddr_in.in_addr.s_addr
//in_addr_t inet_addr(const char *cp);
//把字符串格式的IP转换成大端序的IP,转化后的IP直接填充到sockaddr_in.in_addr成员
//int inet_aton(const char * cp,struct in_addr *inp)
//把大端序的IP转化成字符串形式的IP,用于在服务端程序中解析客户端的IP地址
//char *inet_nota(struct in_addr in)
#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<unistd.h>
#include<netdb.h>
using namespace std;
int main(int argc,char *argv[])
{
if(argc!=2)
{
cout<<"Using: ./server 5005\n";
return -1;
}
//第一步,创建socket
int listenfd =socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(listenfd==-1)
{
perror("socket");return -1;
}
//第二步,把服务端用于通信的IP和端口绑定到socket上
// struct sockaddr_in {
// short int sin_family; // 协议族,通常为 AF_INET
// unsigned short int sin_port; // 端口号(网络字节顺序)
// struct in_addr sin_addr; // IP地址
// unsigned char sin_zero[8]; // 填充字段,使结构体大小与 sockaddr 一致
// };
struct sockaddr_in servaddr;
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(atoi(argv[1]));
servaddr.sin_addr.s_addr=htonl(INADDR_ANY); //将32位主机字节顺序(Host Byte Order)的整数转换为网络字节顺序
//第三步,绑定服务端的IP和端口
if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr))==-1)
{
perror("bind");close(listenfd);return -1;
}
//第四步,把socket设置为可监听的状态
if(listen(listenfd,5)==-1) //5表示挂起连接请求的最大队列长度
{
perror("listen");close(listenfd);return -1;
}
//第五步,受理连接请求,accept为阻塞等待
int sockfd=accept(listenfd,0,0);
if(sockfd==-1)
{
perror("accept");close(listenfd);return -1;
}
char buffer[1024];
int iret;
while(true)
{
memset(buffer,0,sizeof(buffer));
if((iret=recv(sockfd,buffer,sizeof(buffer),0))<=0)
{
cout<<"iret="<<iret<<endl;close(listenfd);return -1;
}
cout<<"接收="<<buffer<<endl;
strcpy(buffer,"ok!\n");
if(iret=send(sockfd,buffer,sizeof(buffer),0)<=0)
{
cout<<"iret="<<iret<<endl;close(listenfd);return -1;
}
}
return 0;
}
浙公网安备 33010602011771号