Linux网络编程:简单的客户端服务器模型

简单的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);      
    }
}  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值