套接字是两个进程序通信的一种方法,而且可以是两台不同的主机之间的进程通信。套接字通信类似向“文件”读写进行通信,而这个“文件”
就是套接字,某个大神曾经说过:"Linux的哲学思想之一就是一切皆文件"。下图就是经典的客户端服务器端模型
一.sock的基本API函数
1.socket()
Type 指明套接口类型 取值
SOCK_STREAM 字节流套接口 1
SOCK_DGRAM 数据报套接口 2
SOCK_SEQPACKET 有序分组套接口 5
SOCK_RAW 原始套接口 3
SOCK_PACKET 数据链路套接口 10 (只有Linux支持)
Protocol 指定协议的类型, 取值:
IPPROTO_TCP TCP传输协议 6
IPPROTO_UDP UDP传输协议 7
IPPROTO_SCTP SCTP传输协议 132
Protocol通常取0,让系统根据family和type的取值,选取合适的protocol的值.
补充:返回的套接字描述符在Linux中本质是一个文件描述符(为了方便记作sockfd),大多数以文件描述符为参数的函数都可以接受使用套接字描述符。
2.bind()绑定套接字
#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
sockfd:socket函数的返回值
myaddr:指向已初始化的sockaddr_in指针,用(structsockaddr*)强制转换。
addrlen:套接口地址结构大小(sizeof(sockaddr)与sizeof(sockaddr_in)一样)
类型socklen_t:unsigned int
返回值:成功为0,出错为-1
(1)地址格式与初始化
由于两台计算机通信时进程IP已无法区别两个进程,所以套接字通信用(ip:端口号)标识两个通信的进程。而Linux系统的IPV4(AF_INET)中,套接字地址结构用
sockaddr_in表示,bind()任务就是绑定sockfd和初始化的sockaddr_in。
头文件<netinet/in.h>
structsockaddr_in
{
unsigned short sin_family; //协议族(四个字节,不同的机型不一定是unsigned short)
unsigned shortsin_port; //端口号(两个字节情况同上)
unsignedintsin_addr.s_addr; //IP地址
unsigned charsin_zero[8]; //填充字段
};
*初始化的sockaddr_in
1)必须memset把它初始化为0,然后再填充内容。
2)sin_family:必须用htons(端口号)把端口号转化为网络字节序再赋值给sin_family。而且端口号必须大于1024,应为小于1024都给系统占用。
3)sin_addr.s_addr:客户端有多个网络接口(IP地址)时多使用XX.sin_addr.s_addr=htonl(INADDR_ANY)表示服务器可以在任意网络接口上接受客户连接。
当服务器要求绑定指定接口,或客户端在初始化目的服务器的地址时,要用inet_pton指定IP地址。
int inet_pton(intdomain,const char* str, void *addr);
参数
3.listen()(服务器特有)
<sys/socket.h>
int listen(int sockfd,int backlog);
返回值:成功返回0,出错返回-1
listen将套接口从主动态变成被动态即进入LISTEN状态,表示服务器愿意接收请求。
listen的第一个参数为sock()返回的套接字描述符,listen的第二个参数规定了内核应该为相应套接口排队的最大连接个数,Linux的上限为SOMAXCONN.
4.accept()(服务器特有)
#include<sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr,socklen_t *addrlen);
成功:非负新的套接字描述符(记作new_sockfd),代表与所返回客户的TCP连接(sockfd用来监听套接口,new_sockfd为已连接套接口用来read/write或send /recv)
出错:-1
当没有客户端连接请求时服务器将在这里阻塞,直到有客户端connect()。
sockfd:即socket函数的返回值,套接字
cliaddr:当accept函数返回时,内核将从已完成连接队列中,取出一个连接;并将该连接的客户端信息(),保存在cliaddr指向的结构体中
addrlen:调用accept前,*addrlen的值为cliaddr实际所指的套接口地址结构的长度;accept返回时,内核填充*addrlen,该值为确切地客户套接口地址结构长度
当不需要利用客户端的地址,可以将cliaddr和addrlen设置为NULL。
5.connect()(客户端特有)
int connect(int sockfd, const struct sockaddr*servaddr, socklen_t addrlen);
sockfd:socket()申请到的套接字描述符。
servaddr:服务器的地址信息(这说明了要在客户端程序里初始化一次目的服务器sockaddr_in,但不需要在客户端中再bind()一次)
addrlen:服务器套接口地址结构的长度,也就是sizeof(sockaddr)或sizeof(sockaddr_in)
返回值:成功返回0,出错返回-1
6.收发数据
当客户端connect()且服务器accept()后,两者建立连接,然后利用数据收发函数通信Write()、read()、send()、recv()、recvfrom/sendto()(与send()一样,但可用于 无连接通信)进行交换数据。
×客户 端: write/read(client_sockfd,….)表示向服务器“发/收”数据。
×服务器端:write/read(new_server_sockfd,….)表示向客户端“发/收”数据。这里的new_server_sockfd是服务器accept后返回的新套接字描述符。
7.close()
如果服务器accept()成功,要close()两次套接字,分别为new_server_sockfd与server_sockfd。
#include<iostream>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
/*
TCPclient创建一个客户端对象,通过接受连接的目的服务器的端口号,目的服务器IP地址两个参数来构造一个
基于字节流的TCP客户端对象
*/
class TCPclient
{
public:
TCPclient(uint16_t port,const char* IP)
{
c_port=port;
c_IP=new char[strlen(IP)+1];
memcpy(c_IP,IP,strlen(IP)+1);
}
~TCPclient()
{
delete []c_IP;
}
bool TCPwork()
{
struct sockaddr_in server_addr;
int client_socket=socket(AF_INET,SOCK_STREAM,0);
if(client_socket==-1)
{
std::cout<<"creat socket fail!"<<std::endl;
return 0;
}
memset(&server_addr1,0,sizeof(sockaddr_in));//初始化目的服务器的struct sockaddr_in结构
server_addr1.sin_family=AF_INET;
server_addr1.sin_port=htons(c_port);
int i=inet_pton(AF_INET,c_IP,&server_addr.sin_addr);
if(i==-1)
{
std::cout<<"make serversock fail!"<<std::endl;
return 0;
}
int j=connect(client_socket,(sockaddr *)&server_addr1,(socklen_t)sizeof(sockaddr));
if(j==-1)
{
std::cout<<"connect socket fail!"<<std::endl;
return 0;
}
client_funtion(client_socket);
close(client_socket);
return 1;
}
private:
virtual void client_funtion(int sockfd)//实现某种数据交换功能的虚函数
{
char buf[20];
read(sockfd,buf,sizeof(buf));
std::cout<<buf<<std::endl;
}
private:
uint16_t c_port;
char *c_IP;
};
main()
{
CTPclient client1(2000,"192.168.178.20");
client1.TCPwork();
}
#include<iostream>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
/*
TCPserver是一个基于TCP 字节流的服务器对象
*/
class TCPserver
{
public:
TCPserver(int listenquene=SOMAXCONN, uint16_t port=2000,char* IP=NULL)//接收监听队列最大值,服务器端口,服务器IP地址三个参数来构造对象
{
C_listenquene=listenquene;
C_port=port;
if(IP==NULL)
{
C_IP=IP;
}
else
{
C_IP=new char[strlen(IP)+1];
memcpy(C_IP,IP,strlen(IP)+1);
}
}
~TCPserver()
{
if(C_IP!=NULL)
delete [] C_IP;
}
bool TCPserver_work()
{
struct sockaddr_in s_addr1, s_addr2;
int server_socket=socket(AF_INET,SOCK_STREAM,0);
if(server_socket==-1)
{
std::cout<<"creat socket fail!"<<std::endl;
return 0;
}
memset(&s_addr1,0,sizeof(s_addr1));
s_addr1.sin_family=AF_INET;
s_addr1.sin_port=htons(C_port);
if(C_IP==NULL)//当C-ip为空值时,服务器IP地址接收为设置为INADDR_ANY。可以接受连接到服务器所在主机所有网卡接口的客户端连接请求。
{
s_addr1.sin_addr.s_addr=htonl(INADDR_ANY);
}
else、//设置为固定某个网卡接口,
{
int i=inet_pton(AF_INET,C_IP,(void*)(&s_addr1.sin_addr.s_addr));
if(i!=1)
{
std::cout<<"inet_pton fail!"<<std::endl;
return 0;
}
}
int j=bind(server_socket,(sockaddr*)&s_addr1,sizeof(sockaddr));
if(j==-1)
{
std::cout<<"bind socket fail!"<<std::endl;
return 0;
}
int i=listen(server_socket,C_listenquene);
if(i==-1)
{
std::cout<<"listen socket fail!"<<std::endl;
return 0;
}
socklen_t lenght_addr= sizeof(sockaddr);//注意accept最后一个参数
int new_socket=accept(server_socket,(sockaddr*)&s_addr2,&lenght_addr);
if(new_socket==-1)
{
std::cout<<"accep socket fail!"<<std::endl;
return 0;
}
server_funtion(new_socket);
· //std::cout<<"write:hellow world!"<<std::endl;
close(new_socket);
close(server_socket);
return 1;
}
private:
virtual void server_funtion(int sockfd)
{
}
private:
int C_listenquene;
uint16_t C_port;
char* C_IP;
};
class myTCPserver :public TCPserver
{
public:
myTCPserver(int listenquene=SOMAXCONN,uint16_t port=2000,char* IP=NULL):TCPserver(listenquene,port,IP)
{
}
~myTCPserver()
{
}
private:
void server_funtion(int sockfd)
{
write(sockfd,"========hellow Mir.li!",23);
std::cout<<"write:hellow Mir.li!"<<std::endl;
}
};
main()
{
myTCPserver server1;
server1.TCPserver_work();
return 0;
}