本章主要是对套接字网络编程的一个学习,目标是能够基本的进行套接字编程
无论是服务端还是客户端,进行网络编程需要做的第一件事就是创建套接字
int socket(int domain, int type, int protocol);
struct sockaddr
结构的前16位:本地通信设置为AF_UNIX
,网络通信设置为AF_INET
(IPv4)或AF_INET6
(IPv6)SOCK_DGRAM
,对于TCP的流式传输则填入SOCK_STREAM
//创建socket网络文件
int sock=socket(AF_INET,SOCK_DGRAM,0);//ipv4协议,数据报式套接(UDP),套接字协议(0:默认协议)
if(sock < 0)
{
std::cerr<<"socket"<<std::endl;
return 2;
}
std::cout<<"sock:"<<sock<<std::endl;
对于服务端和客户端都要进行绑定ip及port,只有绑定后才能标识网络中唯一的主机中的进程服务,便于进程接下来的数据传输
struct sockaddr_in
成员:sin_family:表示协议家族 sin_port:表示端口号,是一个16位的整数 sin_addr:表示IP地址,是一个32位的整数 sin_addr中的成员s_addr:表示IP地址,是一个32位的整数
in_addr_t inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);
in_addr
,不需要选中in_addr
结构当中的32位的成员传入,直接传入in_addr
结构体即可注:上述函数在转化ip格式时同时也会自动进行网络字节序的转化
服务端创建套接字,即底层打开了对应的网络套接字文件,想进行网络通信还需要绑定对应的网络信息,即将套接字文件与网络进行强相关
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//创建套接字结构体-填入ip及port
struct sockaddr_in local;
memset(&local,0,sizeof(local));//初始化结构体
local.sin_family=AF_INET;//通信协议-ipv4
local.sin_port=htons(atoi(argv[1]));//使用命令行参数+网络字节序转化接口
local.sin_addr.s_addr=htons(INADDR_ANY);//云服务器不建议绑定明确的ip,建议使用INADDR_ANY绑定该主机所有设备
//将网络文件与套接字进行绑定(强相关)
if(bind(sock,(struct sockaddr*)&local,sizeof(local))==-1)
{
std::cout<<"bind"<<std::endl;
return 3;
}
//填入目标套接字的信息-确定传输数据的对象
struct sockaddr_in desc;
memset(&desc,sizeof(desc),0);
desc.sin_family=AF_INET;//通信的ip协议-ipv4
desc.sin_port=htons(atoi(argv[2]));//字符串转整数+网络字节序转化
desc.sin_addr.s_addr=inet_addr(argv[1]);//点分十进制字符串ip转四字节整数ip-自动转化为网络字节序
//客户端不用主动绑定ip和port,当向远端发送消息是会自动绑定-服务端才需要固定的ip及port
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
注:由于UDP不是面向连接的,所以传输数据时需要指明对端网络相关的信息,即sendto的最后两个参数用来表示对端的信息
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
注:recvfrom接口的倒数第二个参数是一个输出型参数,用于获取发送消息的对端网络信息,这样就知道是谁发的数据,并可以进一步向对端做出回应
//进行获取远端消息并回复
while(1)
{
char buffer[128]={0};
//接收远端的套接字信息-便于进行回复
struct sockaddr_in peer; //接收对端信息
socklen_t len=sizeof(peer);
ssize_t s=recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len); //接收数据
if(s>0)
{
buffer[s]=0;
std::cout<<"client# "<<buffer<<std::endl;
sendto(sock,buffer,strlen(buffer),0,(struct sockaddr*)&peer,len);//回显传输
}
}
while(1)
{
std::cout<<"Please Enter# ";//提示符
fflush(stdout);//刷新缓冲区
char buffer[128]={0};
ssize_t size=read(0,buffer,sizeof(buffer)-1);//保存键盘输入数据
if(size>0)
{
buffer[size-1]=0;//覆盖回车符
sendto(sock,buffer,strlen(buffer),0,(struct sockaddr*)&desc,sizeof(desc));//向服务端发送消息
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
ssize_t s=recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);//接收消息
if(s>0)
{
buffer[s]=0;
std::cout<<"Echo# "<<buffer<<std::endl;
}
}
}
当服务端收到客户端发来的数据后,除了在服务端进行打印以外,服务端可以调用sento函数将收到的数据重新发送给对应的客户端,以此测试双端的数据的收发功能
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstring>
#include <unistd.h>
//命令行参数的使用
int main(int argc,char* argv[])
{
//程序启动
if(argc!=2)
{
std::cerr<<"Usage: udpserver port"<<std::endl;
return 1;
}
//创建socket网络文件
int sock=socket(AF_INET,SOCK_DGRAM,0);//ipv4协议,数据报式套接,套接字协议(0:默认协议)
if(sock < 0)
{
std::cerr<<"socket"<<std::endl;
return 2;
}
std::cout<<"sock:"<<sock<<std::endl;
//创建套接字结构体-填入ip及port
struct sockaddr_in local;
memset(&local,0,sizeof(local));//初始化结构体
local.sin_family=AF_INET;//通信协议-ipv4
local.sin_port=htons(atoi(argv[1]));//使用命令行参数+网络字节序转化
local.sin_addr.s_addr=htons(INADDR_ANY);//云服务器不建议绑定明确的ip,建议使用INADDR_ANY绑定该主机所有设备
//将网络文件与套接字进行绑定(强相关)
if(bind(sock,(struct sockaddr*)&local,sizeof(local))==-1)
{
std::cout<<"bind"<<std::endl;
return 3;
}
//进行获取远端消息并回复
while(1)
{
char buffer[128]={0};
//接收远端的套接字信息-便于进行回复
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
ssize_t s=recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
if(s>0)
{
buffer[s]=0;
std::cout<<"client# "<<buffer<<std::endl;
sendto(sock,buffer,strlen(buffer),0,(struct sockaddr*)&peer,len);
}
}
close(sock);
return 0;
}
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
int main(int argc,char* argv[])
{
if(argc!=3)
{
std::cerr<<"Usage: udp_client desc_ip desc_port"<<std::endl;
return 1;
}
//创建网络文件
int sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0)
{
std::cerr<<"Usage: udp_client desc_ip desc_port"<<std::endl;
}
//填入目标套接字的信息
struct sockaddr_in desc;
memset(&desc,sizeof(desc),0);
desc.sin_family=AF_INET;//通信的ip协议-ipv4
desc.sin_port=htons(atoi(argv[2]));//字符转数字-传输格式转化
desc.sin_addr.s_addr=inet_addr(argv[1]);//点分十进制字符转网络-自动会将主机格式转为网络格式
//客户端不用主动绑定ip和port,当向远端发送消息是会自动绑定-服务端才需要固定的ip及port
while(1)
{
std::cout<<"Please Enter# ";
fflush(stdout);
char buffer[128]={0};
ssize_t size=read(0,buffer,sizeof(buffer)-1);
if(size>0)
{
buffer[size-1]=0;//覆盖回车符
sendto(sock,buffer,strlen(buffer),0,(struct sockaddr*)&desc,sizeof(desc));
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
ssize_t s=recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
if(s>0)
{
buffer[s]=0;
std::cout<<"Echo# "<<buffer<<std::endl;
}
}
}
close(sock);
return 0;
}
相比于UDP套接字来说,TCP套接字与之在一些地方是相同的,但是TCP的特点是面向链接的流式套接字,所以还是有很大的区别的
同样的tcp的服务端和客户端首先第一件事是创建套接字文件
int socket(int domain, int type, int protocol);
//1.创建socket网络文件
int sock=socket(AF_INET,SOCK_STREAM,0);//ipv4协议,流式套接(TCP),套接字协议(0:默认协议)
if(sock < 0)
{
std::cerr<<"socket"<<std::endl;
return 2;
}
std::cout<<"sock:"<<sock<<std::endl;
tcp的服务端和客户端接下来的事也是填写ip/port和绑定
//2.绑定port号及ip地址
struct sockaddr_in local;
bzero(&local,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
cerr<<"bind"<<endl;
exit(2);
}
//2.创建socketaddr_in结构体并填入服务端的信息
struct sockaddr_in desc;
bzero(&desc,sizeof(desc));
desc.sin_family=AF_INET;//传输协议
desc.sin_port=htons(desc_port);//端口号
desc.sin_addr.s_addr=inet_addr(desc_ip.c_str());//IP地址-点分十进制转四字节ip同时转成网络传输格式
//客户端并不用进行绑定自己的端口-发送数据时会自动进行绑定
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//3.建立监听-允许client进行链接server
if(listen(listen_sock,backlog)<0)
{
cerr<<"listen"<<endl;
exit(3);
}
//和client建立连接关系
struct sockaddr_in peer;//获取客户端的信息
socklen_t len=sizeof(peer);
bzero(&peer,len);
int sock=accept(listen_sock,(struct sockaddr*)&peer,&len);//真正进行服务的网络文件
if(sock<0)
{
cout<<"accept error"<<endl;
}
//客户端也不用进行listen-客户端是发起链接的一方,服务端是接收链接的需要保证listen状态
//3.发起链接
if(connect(sock,(struct sockaddr*)&desc,sizeof(desc)) < 0)
{
cerr<<"connect"<<endl;
}
ssize_t read(int fd, void *buf, size_t count);
注:如果客户端将连接关闭了,那么此时服务端将套接字当中的信息读完后就会读取到0,不必再为该客户端提供服务了
ssize_t write(int fd, const void *buf, size_t count);
除了使用文件读写函数接口进行发送和接收网络数据,还可以使用专门的数据发送和接收接口
int send(SOCKET s,const char FAR *buf ,int len ,int flags);
int recv(SOCKET s ,char FAR * buf ,int len ,int flags);
注:因为TCP是面向链接的,每一个读写的套接字文件都已经确立了对应的链接对象,所以这里的recv和send并不用像UDP的recvfrom和sendto那样指定对端的网络信息
while(true)
{
char buffer[1024]={0};
//接收客户端的消息
ssize_t s=read(sock,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s]=0;
cout<<"client# "<<buffer<<endl;
string message=buffer;
if(message=="quit")
{
write(sock,"quit success!",strlen("quit success!"));
break;
}
message+="[server_echo]";
write(sock,message.c_str(),message.size());
}
else if(s==0)
{
cout<<"client close..."<<endl;
break;
}
else
{
cerr<<"read error"<<endl;
break;
}
}
//4.执行逻辑
while(true)
{
char buffer[1024]={0};
cout<<"Please Enter#";//提示符
fflush(stdout);
ssize_t s=read(0,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s-1]=0;//覆盖回车键
//向服务端传数据-tcp是流式套接字
write(sock,buffer,strlen(buffer));
//接收服务端发来的数据
ssize_t size=read(sock,buffer,sizeof(buffer)-1);
if(size>0)
{
buffer[size]=0;
cout<<buffer<<endl;
}
else
{
cerr<<"server close"<<endl;
break;
}
}
}
#pragma once
#include "server.hpp"
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <string.h>
#include <string>
#include <map>
//执行处理逻辑
std::map<std::string,std::string> dict={
{"apple","苹果"},
{"banana","香蕉"},
{"hello","你好"},
};
void HandlerTranslation(int sock)
{
cout<<"进行翻译处理: debug sock: "<<sock<<endl;
while(true)
{
char buffer[1024]={0};
//接收客户端的消息
ssize_t s=read(sock,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s]=0;
cout<<"client# "<<buffer<<endl;
string message=buffer;
if(message=="quit")
{
write(sock,"quit success!",strlen("quit success!"));
break;
}
message+="[server_echo]";
write(sock,message.c_str(),message.size());
}
else if(s==0)
{
cout<<"client close..."<<endl;
break;
}
else
{
cerr<<"read error"<<endl;
break;
}
}
}
void* Routine(void* args)
{
int sock=*(int*)args;
delete (int*)args;
//线程分离-不用进行等待
pthread_detach(pthread_self());
HandlerTranslation(sock);
close(sock);//线程自己进行关闭
return nullptr;
}
//多线程-轻量化,健壮性不足
void HandlerSock(int sock)
{
pthread_t tid;
int* p=new int(sock);
pthread_create(&tid,nullptr,Routine,p);
}
server.hpp:
#pragma once
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <stdlib.h>
#include <string>
using std::cout;
using std::cerr;
using std::endl;
using std::string;
const int backlog=5;
typedef void(*Handler)(int);//函数指针类型
class TcpServer
{
private:
uint16_t _port;//绑定的port
int listen_sock;//监听套接字
public:
TcpServer(uint16_t port)
:_port(port),listen_sock(-1)
{}
void InitTcpServer()
{
//1.创建listen_socket套接字文件
listen_sock=socket(AF_INET,SOCK_STREAM,0);//ipv4协议,socket类型,传输协议编号
if(listen_sock < 0)
{
cerr<<"listen_sock"<<endl;
exit(1);
}
//2.绑定port号及ip地址
struct sockaddr_in local;
bzero(&local,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
cerr<<"bind"<<endl;
exit(2);
}
//3.建立监听-允许client进行链接server
if(listen(listen_sock,backlog)<0)
{
cerr<<"listen"<<endl;
exit(3);
}
}
void Loop(Handler handler)
{
//4.执行逻辑-接收链接并进行处理服务
while(true)
{
//和client建立连接关系
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
bzero(&peer,len);
int sock=accept(listen_sock,(struct sockaddr*)&peer,&len);//真正进行服务的网络文件
if(sock<0)
{
cout<<"accept error"<<endl;
continue;
}
//验证-输出链接client端信息
uint16_t port=ntohs(peer.sin_port);//网络转主机序列
string ip=inet_ntoa(peer.sin_addr);//4字节ip转点分十进制ip
cout<<"debug: sock#"<<sock<<" peer_ip#"<<ip<<" peer_port#"<<port<<endl;
//执行处理逻辑
handler(sock);
}
}
~TcpServer()
{
if(listen_sock > 0)
close(listen_sock);
}
};
server.cc:
#include "server.hpp"
#include "Handler.hpp"
int main(int argc,char* argv[])
{
if(argc!=2)
{
cout<<"Usage:\n\t"<<"tco_server port"<<endl;
return 1;
}
uint16_t port=atoi(argv[1]);
TcpServer ser(port);
ser.InitTcpServer();
ser.Loop(HandlerSock);
return 0;
}
client.hpp:
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <cstring>
#include <stdio.h>
using std::cout;
using std::cerr;
using std::endl;
using std::string;
class TcpClient
{
private:
string desc_ip;//要访问的目标端ip
uint16_t desc_port;//要访问的目标端port
int sock;//打开的网络文件
public:
TcpClient(string ip,uint16_t port)
:desc_ip(ip),desc_port(port),sock(-1)
{
//进行初始化工作
//1.创建socket套接字文件
sock=socket(AF_INET,SOCK_STREAM,0);//ipv4协议,socket类型,传输协议编号
if(sock < 0)
{
cerr<<"sock"<<endl;
exit(1);
}
}
void start()
{
//2.创建socketaddr_in结构体并填入服务端的信息
struct sockaddr_in desc;
bzero(&desc,sizeof(desc));
desc.sin_family=AF_INET;//传输协议
desc.sin_port=htons(desc_port);//端口号
desc.sin_addr.s_addr=inet_addr(desc_ip.c_str());//IP地址-点分十进制转四字节ip同时转成网络传输格式
//对于客户端并不用进行绑定自己的端口-发送数据时会自动进行绑定
//客户端也不用进行listen-客户端是发起链接的一方,服务端是接收链接的需要保证listen状态
//3.建立链接关系
if(connect(sock,(struct sockaddr*)&desc,sizeof(desc)) < 0)
{
cerr<<"connect"<<endl;
}
//4.执行逻辑
while(true)
{
char buffer[1024]={0};
cout<<"Please Enter#";//提示符
fflush(stdout);
ssize_t s=read(0,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s-1]=0;//覆盖回车键
//向服务端传数据-tcp是流式套接字
write(sock,buffer,strlen(buffer));
//接收服务端发来的数据
ssize_t size=read(sock,buffer,sizeof(buffer)-1);
if(size>0)
{
buffer[size]=0;
cout<<buffer<<endl;
}
else
{
cerr<<"server close"<<endl;
break;
}
}
}
}
~TcpClient()
{
if(sock >= 0)
close(sock);
}
};
client.cc:
#include "tcp_client.hpp"
int main(int argc,char* argv[])
{
if(argc!=3)
{
cout<<"Usage:\n\t"<<"tcp_client desc_ip desc_port"<<endl;
return 1;
}
//写入ip和port
uint16_t port=atoi(argv[2]);
string ip=argv[1];
TcpClient client(ip,port);
client.start();
return 0;
}