简单的C/S模型
简单的服务端工作流程
- socket():创建服务端的socket
- bind():将通信地址和端口号绑定到socket上
- listen():把socket设置为监听模式(TCP3次握手)
- accept():接受客户端的连接
- recv()/send():与客户端通信
- close():关闭socket
客户端工作流程
- socket():创建客户端的socket
- connect():向服务端发起连接请求(TCP3次握手)
- send()/recv():与服务端通信
- close():关闭socket
代码示例
服务端代码
//code01.cpp code_tree
#include<sys/socket.h>
#include<netinet/in.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<assert.h>
#define BUFFER_SIZE 1024
int main()
{
//第一步:创建服务端的socket
int sockfd=socket(PF_INET,SOCK_STREAM,0);
assert(socket>=0);
//第二步:绑定地址与端口号
int ret=0;
struct sockaddr_in address;
memset(&address,0,sizeof(address));
address.sin_family=AF_INET;
address.sin_addr.s_addr=htonl(INADDR_ANY);
address.sin_port=htons(5051);
ret=bind(sockfd,(struct sockaddr*)&address,sizeof(address));
assert(ret!=-1);
//第三步:把socket设置为监听模式
ret=listen(sockfd,5);
assert(ret!=-1);
//第四步:接受客户端连接
struct sockaddr_in client;
socklen_t clientlen=sizeof(client);
int connfd=accept(sockfd,(struct sockaddr*)&client,&clientlen);
assert(connfd!=-1);
printf("connected with ip %s\n",inet_ntoa(client.sin_addr));
//第五步:与客户端通信
char buffer[BUFFER_SIZE];
while(1)
{
memset(buffer,0,BUFFER_SIZE);
ret=recv(connfd,buffer,BUFFER_SIZE-1,0);
if(ret<=0)
{
printf("ret=%d\n",ret);
break;
}
printf("ret=%d,recv:%s\n",ret,buffer);
strcpy(buffer,"成功接收");
ret=send(connfd,buffer,strlen(buffer),0);
if(ret<=0)
break;
}
//第六步:关闭连接
close(sockfd);
close(connfd);
}
客户端代码
//code02.cpp code_tree
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define BUFFER_SIZE 1024
int main(int argc,char* argv[])
{
if(argc<=1)
return 1;
const char* ip=argv[1];
//第一步:创建客户端socket
int sockfd=socket(PF_INET,SOCK_STREAM,0);
assert(sockfd>=0);
//第二步:向服务器发起连接
int ret=0;
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family=AF_INET;
inet_pton(AF_INET,ip,&server.sin_addr);
server.sin_port=htons(5051);
ret=connect(sockfd,(struct sockaddr*)&server,sizeof(server));
if(ret!=0)
{
printf("connection failed\n");
close(sockfd);
return -1;
}
//第三步:与服务端通信
char buffer[BUFFER_SIZE];
for(int i=0;i<3;++i)
{
memset(buffer,0,BUFFER_SIZE);
strcpy(buffer,"数据");
ret=send(sockfd,buffer,strlen(buffer),0);
if(ret<=0)
break;
printf("ret=%d,发送%d次%s\n",ret,i+1,buffer);
memset(buffer,0,BUFFER_SIZE);
ret=recv(sockfd,buffer,BUFFER_SIZE-1,0);
if(ret<=0)
break;
printf("ret=%d,接收:%s\n",ret,buffer);
}
//第四步:关闭socket
close(sockfd);
}
注意事项
socket函数
#include<sys/types.h>
#include<sys/socket.h>
int sockfd=socket(PF_INET,SOCK_STREAM,0);
assert(socket>=0);
-
socket函数的第一个参数告诉系统使用哪个底层协议族(IPV4:PF_INET,IPV6:PF_INET6,UNIX:PF_UNIX)。
-
第二个参数指服务类型,主要有SOCK_STREAM(TCP流服务)和SOCK_UGRAM(UDP数据报服务)。
-
第三个参数表示再选择一个具体协议,几乎所有情况把它设置为0,表示使用默认协议。
-
socket系统调用成功时返回一个socket文件描述符,失败则返回-1并设置errno。
字节序转换函数
主机字节序为小端字节序,网络字节序为大端字节序。
#include<netinet/in.h>
address.sin_addr.s_addr=htonl(INADDR_ANY);//将本机任意地址转换为网络字节序并设置为服务器地址
address.sin_port=htons(5051);//指定5051为端口号
- htonl、htons、ntohl、ntohs 4个函数用来完成主机字节序和网络字节序之间的转换。其中htonl表示”host to network long“,即长整型的主机字节序数据转换为网络字节序。
- 通常长整型函数用来转换IP地址,短整型函数用来转换端口号。
IP地址转换函数
我们习惯用点分十进制字符串表示IPv4,用冒号十六进制字符串表示IPv6。在网络编程中,我们要将它们转换为二进制整数使用。
#include<arpa/inet.h>
address.sin_addr.s_addr = inet_addr("192.168.1.0"); //指定ip地址转换为网络字节序并设置为服务器地址
printf("connected with ip %s\n",inet_ntoa(client.sin_addr));//将客户端ip地址转换为点分十进制表示的地址并打印
inet_pton(AF_INET,ip,&server.sin_addr);//将参数ip(点分十进制表示的IPv4地址)转换为网络字节序表示的IP地址并将转换结果设置为服务地址
另外两个实例中没有用到的IP地址转换函数
int inet_aton(const char* cp,struct in_addr* inp);
const char* inet_ntop(int af,const void* src,char* dst,socklen_t cnt);
-
函数原型较难看懂,其中inet_aton与inet_addr类似,但其将转换结果存储到inp指向的地址结构中。
-
inet_ntop是inet_pton进行相反的转换,前三个参数与inet_pton的相同,最后一个参数cnt为指定存储单元的大小。
-
inet_addr,inet_ntoa,inet_aton三个函数仅用于IPv4,另外两个函数可用于IPv4或IPv6。
-
通常端口号用字节序转换函数,IP地址则用IP地址转换函数即可。
socket个数
//服务端
int sockfd=socket(PF_INET,SOCK_STREAM,0);
int connfd=accept(sockfd,(struct sockaddr*)&client,&clientlen);
//客户端
int sockfd=socket(PF_INET,SOCK_STREAM,0);
- accept成功时返回一个新的连接socket,该socket唯一的标识了被接受的这个连接。
- 服务端有两个socket,一个用来监听socket,一个用来与对应的客户端通信。
代码封装
头文件
//myhead.h code_tree
#ifndef MYHEAD_H
#define MYHEAD_H
#include<sys/socket.h>
#include<netinet/in.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<assert.h>
#define BUFFER_SIZE 1024
class Server
{
private:
int m_listenfd; // 服务端用于监听的socket
int m_connfd; // 客户端连上来的socket
public:
Server();
bool InitServer(int port); // 初始化服务端
bool Accept(); // 等待客户端的连接
int Send(const void *buf,const int buflen);
int Recv(void *buf,const int buflen);
~Server();
};
class Client
{
private:
int m_sockfd;
public:
Client();
bool CliToSer(const char* ip,const int port);
int Send(const void *buf,const int buflen);
int Recv(void *buf,const int buflen);
~Client();
};
#endif
//myhead.cpp code_tree
#include"myhead.h"
Server::Server():m_listenfd(0),m_connfd(0){}
Server::~Server()
{
if(m_listenfd!=0)
close(m_listenfd);
if(m_connfd!=0)
close(m_connfd);
}
bool Server::InitServer(int port)
{
m_listenfd=socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
if(bind(m_listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr))!=0)
{
close(m_listenfd);
m_listenfd=0;
return false;
}
if (listen(m_listenfd,5) != 0 )
{
close(m_listenfd);
m_listenfd=0;
return false;
}
return true;
}
bool Server::Accept()
{
if((m_connfd=accept(m_listenfd,0,0))<=0)
return false;
return true;
}
int Server::Send(const void* buf,const int buflen)
{
return send(m_connfd,buf,buflen,0);
}
int Server::Recv(void* buf,const int buflen)
{
return recv(m_connfd,buf,buflen,0);
}
Client::Client():m_sockfd(0){}
Client::~Client()
{
if(m_sockfd!=0)
close(m_sockfd);
}
bool Client::CliToSer(const char* ip,const int port)
{
m_sockfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
inet_pton(AF_INET,ip,&servaddr.sin_addr);
if (connect(m_sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr))!=0)
{
close(m_sockfd);
m_sockfd=0;
return false;
}
return true;
}
int Client::Send(const void* buf,const int buflen)
{
return send(m_sockfd,buf,buflen,0);
}
int Client::Recv(void* buf,const int buflen)
{
return recv(m_sockfd,buf,buflen,0);
}
服务端代码
//code03.cpp code_tree 简单的服务端
#include"myhead.h"
int main()
{
Server server;
if(server.InitServer(5051)==false)
return -1;
if(server.Accept()==false)
return -1;
printf("connect ok\n");
char buffer[BUFFER_SIZE];
while(1)
{
memset(buffer,0,BUFFER_SIZE);
if(server.Recv(buffer,BUFFER_SIZE-1)<=0)
break;
printf("接收: %s\n",buffer);
strcpy(buffer,"成功接收");
if(server.Send(buffer,strlen(buffer))<=0)
break;
printf("发送: %s\n",buffer);
}
printf("连接断开\n");
}
客户端代码
//code04.cpp code_tree
#include"myhead.h"
#define BUFFER_SIZE 1024
//运行时手动输入服务器的ip地址
int main(int argc,char* argv[])
{
if(argc<=1)
return 1;
const char* ip=argv[1];
Client client;
if(client.CliToSer(ip,5051)==false)
return -1;
char buffer[1024];
int ret=0;
for(int i=0;i<3;++i)
{
memset(buffer,0,BUFFER_SIZE);
strcpy(buffer,"数据");
ret=client.Send(buffer,strlen(buffer));
if(ret<=0)
break;
printf("ret=%d,发送%d次%s\n",ret,i+1,buffer);
memset(buffer,0,BUFFER_SIZE);
ret=client.Recv(buffer,sizeof(buffer));
if(ret<=0)
break;
printf("ret=%d,接收:%s\n",ret,buffer);
}
}