目录
常见的socket API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockaddr结构
socket API
是一层抽象的网络编程接口
,
适用于各种底层网络协议
,
如
IPv4
、
IPv6.
然而
,
各种网络协议的地址格式并不相同
- IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16 位端口号和32位IP地址.
- IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址, 不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
- socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好 处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;
虽然
socket api
的接口是
sockaddr,
但是我们真正在基于
IPv4
编程时
,
使用的数据结构是
sockaddr_in;
这个结构里主 要有三部分信息:
地址类型
,
端口号
, IP
地址
.
![](https://i-blog.csdnimg.cn/blog_migrate/d5c0a961dd60b33044ac01da64d547d0.png)
封装UDPSocket
//udpsocket.cpp
#include<cstdio>
#include<unistd.h>
#include<iostream>
#include<string.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
using namespace std;
class UdpSocket
{
public:
UdpSocket()
:_sockfd(-1)
{}
bool Socket(){
_sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(_sockfd<0)
{
perror("socket error");
return false;
}
return true;
}
bool Bind(const std::string &ip,int port)
{
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(port);
addr.sin_addr.s_addr=inet_addr(ip.c_str());
socklen_t len=sizeof(struct sockaddr_in);
if(bind(_sockfd,(struct sockaddr*)&addr,len)<0)
{
perror("bind error");
return false;
}
return true;
}
bool Send(const std::string &data,const std::string &ip,int port)
{
struct sockaddr_in peeraddr;
peeraddr.sin_family=AF_INET;
peeraddr.sin_port=htons(port);
peeraddr.sin_addr.s_addr=inet_addr(ip.c_str());
socklen_t len=sizeof(struct sockaddr_in);
int ret=sendto(_sockfd,&data[0],data.size(),0,(struct sockaddr*)&peeraddr,len);
if(ret<0)
{
perror("sendto error");
return false;
}
return true;
}
bool Recv(std::string *buf,std::string *ip=NULL,int *port=NULL)
{
struct sockaddr_in peeraddr;
socklen_t len=sizeof(struct sockaddr_in);
char tmp[4096]={0};
int ret=recvfrom(_sockfd,tmp,4095,0,(struct sockaddr*)&peeraddr,&len);
if(ret<0)
{
perror("recvfrom error");
return false;
}
buf->assign(tmp,ret);
if(ip!=NULL)
*ip=inet_ntoa(peeraddr.sin_addr);
if(port!=NULL)
*port=ntohs(peeraddr.sin_port);
return true;
}
bool Close()
{
if(_sockfd>0)
{
close(_sockfd);
_sockfd=-1;
}
return true;
}
private:
int _sockfd;
};
udp客户端代码
#include<iostream>
#include<cstdlib>
#include"udpsocket.cpp"
#include<string.h>
#define CHECK_RET(q) if((q)==false){return -1;}
using namespace std;
int main(int argc,char*argv[])
{
if(argc<3)
{
cout<<"usage:请输入服务端地址信息!\n";
cout<<"\t./udp_client 192.168.1.2 9000\n";
return -1;
}
string srv_ip=argv[1];
int srv_port=atoi(argv[2]);
UdpSocket sock;
CHECK_RET(sock.Socket());
while(1)
{
string buf;
cout<<"client input";
cin>>buf;
CHECK_RET(sock.Send(buf,srv_ip,srv_port));
buf.clear();
CHECK_RET(sock.Recv(&buf));
cout<<"sever response: "<<buf<<endl;
}
sock.Close();
return 0;
}
udp服务端代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<string.h>
int main(int argc,char*argv[])
{
if(argc<3)
{
printf("usage: ./udp_srv 192.168.1.2 9000\n");
return -1;
}
char* srv_ip=argv[1];
int srv_port=atoi(argv[2]);
int sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(sockfd<0)
{
perror("socket error");
return -1;
}
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(srv_port);
addr.sin_addr.s_addr=inet_addr(srv_ip);
socklen_t len=sizeof(addr);
int ret=bind(sockfd,(struct sockaddr*)&addr,len);
if(ret<0)
{
close(sockfd);
perror("bind error");
return -1;
}
//3.接收数据,接收对端地址
while(1){
char buf[4096]={0};
struct sockaddr_in client_addr;
len=sizeof(client_addr);
ret=recvfrom(sockfd,buf,4095,0,(struct sockaddr*)&client_addr,&len);
if(ret<0)
{
close(sockfd);
perror("recvfrom error");
return -1;
}
printf("%s:%d - %s\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),buf);
printf("server input");
fflush(stdout);
memset(buf,0x00,4096);
scanf("%s",buf);
ret=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&client_addr,len);
if(ret<0)
{
close(sockfd);
perror("sendto error");
return -1;
}
}
close(sockfd);
return 0;
}
sockaddr_in
中的成员
struct in_addr sin_addr
表示
32
位 的
IP
地址
但是我们通常用点分十进制的字符串表示
IP
地址
,
以下函数可以在字符串表示 和
in_addr
表示之间转换
;
字符串转in_addr的函数
![](https://i-blog.csdnimg.cn/blog_migrate/2f3a5669a27e127ae7192b62985fb958.png)
in_addr转字符串的函数
![](https://i-blog.csdnimg.cn/blog_migrate/90029bc06a98073d2f9c247852097e88.png)
其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void *addrptr。
封装TCPSocket
#include<cstdio>
#include<iostream>
#include<stdlib.h>
#include<unistd.h>
#include<string>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<stdio.h>
#define MAX_LISTEN 5
#define CHECK_RET(q) if((q)==false){return -1;}
class TcpSocket
{
public:
TcpSocket()
:_sockfd(-1)
{}
bool Socket()
{
_sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(_sockfd<0)
{
perror("socket error");
return false;
}
return true;
}
bool Bind(const std::string&ip,int port)
{
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(port);
addr.sin_addr.s_addr=inet_addr(ip.c_str());
socklen_t len=sizeof(addr);
if(bind(_sockfd,(struct sockaddr*)&addr,len)<0)
{
perror("bind error");
return false;
}
return true;
}
bool Listen(int backlog=MAX_LISTEN)
{
if(listen(_sockfd,backlog)<0)
{
perror("listen error");
return false;
}
return true;
}
bool Connect(const std::string &srv_ip,int srv_port)
{
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(srv_port);
addr.sin_addr.s_addr=inet_addr(srv_ip.c_str());
socklen_t len=sizeof(addr);
if(connect(_sockfd,(struct sockaddr*)&addr,len)<0)
{
perror("connect error");
return false;
}
return false;
}
bool Accept(TcpSocket *new_sock,std::string *cli_ip,int *cli_port)
{
struct sockaddr_in addr;
socklen_t len=sizeof(addr);
int newfd=accept(_sockfd,(struct sockaddr*)&addr,&len);
if(newfd<0)
{
perror("accept error");
return false;
}
std::cout<<"new connect:"<<inet_ntoa(addr.sin_addr)<<":"<<ntohs(addr.sin_port)<<"\n";
new_sock->_sockfd=newfd;
if(cli_ip!=NULL)
{
*cli_ip=inet_ntoa(addr.sin_addr);
}
if(cli_port!=NULL)
{
*cli_port=ntohs(addr.sin_port);
}
return true;
}
bool Send(const std::string &data)
{
ssize_t ret=send(_sockfd,data.c_str(),data.size(),0);
{
if(ret<0)
{
perror("send error");
return false;
}
return true;
}
}
bool Recv(std::string *buf)
{
char tmp[4096]={0};
ssize_t ret=recv(_sockfd,tmp,4096,0);
if(ret<0)
{
perror("recv error");
return false;
}
else if(ret==0)
{
std::cout<<"perr shutdown!\n";
return false;
}
buf->assign(tmp,ret);//从tmp截取指定长度的数据,防止数据里面有字符串结尾标志;
return true;
}
bool Close()
{
if(_sockfd>0)
{
close(_sockfd);
_sockfd=-1;
}
return true;
}
private:
int _sockfd;
};
tcp服务端
#include"tcp_socket.cpp"
int main(int argc,char *argv[])
{
if(argc<2)
{
std::cout<<"usage: ./tcp_srv 9000\n";
return -1;
}
int port = std::stoi(argv[1]);
//创建套接字
TcpSocket lst_sock;
CHECK_RET(lst_sock.Socket());
//绑定地址信息
CHECK_RET(lst_sock.Bind("0.0.0.0",port));
//开始监听
CHECK_RET(lst_sock.Listen());
while(1)
{
TcpSocket new_sock;
std::string cli_ip;
int cli_port;
CHECK_RET(lst_sock.Accept(&new_sock,&cli_ip,&cli_port));
std::cout<<"new connect: "<<cli_ip<<":"<<cli_port<<"\n";
std::string buf;
new_sock.Recv(&buf);//通过新建连接与指定客户端进行通信
std::cout<<"client say: "<<buf<<std::endl;
buf.clear();
std::cout<<"server say: ";
std::cin>>buf;
new_sock.Send(buf);
}
lst_sock.Close();
return 0;
}
tcp客户端
#include"tcp_socket.cpp"
int main(int argc,char*argv[])
{
//客户端虽然不用主动绑定地址,但是必须知道服务端的地址;
if(argc<3)
{
std::cout<<"usage: arg error\n";
std::cout<<"\t ./tcp_cli 192.168.2.2 9000\n";
return -1;
}
int srv_port=std::stoi(argv[2]);
std::string srv_ip=argv[1];
TcpSocket sock;
//创建套接字
CHECK_RET(sock.Socket());
//向服务端发起连接请求
CHECK_RET(sock.Connect(srv_ip,srv_port));
while(1)
{
std::string data;
std::cout<<"client say:";
fflush(stdout);
std::cin>>data;
sock.Send(data);
CHECK_RET(sock.Send(data));
data.clear();
CHECK_RET(sock.Recv(&data));
std::cout<<"server say:"<< data<<std::endl;
}
// 关闭套接字
sock.Close();
}