首先,send()函数和write()可以用于发送,而recv()和read()k可用于接收文件,其本质就是因为linux中,一切皆是文件。
int socket(int domain, int type, int protocol);
domain是指域,是ipv4还是ipv6;type是socket类型(SOCK_STREAM->TCP);protocol是指协议。
看机构很像open函数,用于打开一个网络连接,成功则返回一个网络文件描述符。
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
const struct sockaddr *address是通信地址与端口,其不对IPV4/6区分;socklen_t address_len是指addr大小。
int listen(int socket, int backlog);
backlog是监听队列的长度。
int connect(int socket, const struct sockaddr *address,
socklen_t address_len);
const struct sockaddr *address要连接谁。
ssize_t send(int socket, const void *buffer, size_t length, int flags);
ssize_t recv(int socket, void *buffer, size_t length, int flags);
不难发现,这两个函数比write多一个参数flags。flags本质是看使用的协议支持不支持flags的使用。
辅助地址转化函数:点分十进制与32位字符串进行转换
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);
//上面这些是老的IPV4接口
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
int inet_pton(int af, const char *src, void *dst);
这是新的ipv4/6接口
ntop是将32转为点分十进制;pton将点分十进制转为32字符串进行转换。
struct socketaddr 在linux中用来表示IP地址的标准结构体(IPV4/6实际上用struct sockaddr_in这是V4,sockaddr_in6是V6的填充)。
typedef uint32_t in_addr_t是网络用于表示IP地址的类型;struct in_addr封装了in_addr_t s_addr。
设IPADDR "192.168.1.102"
in_addr_t addr=0;
addr=inet_addr(IPADDR)=0X66 01 a8 c0;
翻译为点分十进制就是102 1 168 192 这是大小端的问题,反过来看就对了
大小端在网络传输中是具有危害的,为了修复这个问题就产生了网络字节区(大端模式)。
inet_pton(AF_INEF,IPADDR,&addr);
AF_INEF表示使用了IPV4
addr是struct in_adr addr,addr.s_addr=0x6601a8c0
ret=inet_ntop(AF_INET,&addr,buf,sizeof(buf));
端口号实际上就是一个数字编号,用于在一台电脑OS中唯一辨识一个能够上网的进程。端口号会被打包进入数据进行传输,目标包是IP+端口号做传输标识。
服务器部分
int main()
{
#define SERADDR "xxx.xxx.xxx.xxx"
int sockfd=-1;
sockfd=socket(AF_INET,SOCK_STREAM,0);//ipv4,TCP,默认协议
if(-1==sockfd)
{
perror("socket");//错误检测
}
struct sockaddr_in seraddr={0};
seraddr.sin_family=AF_INET;//IPV4
seraddr.sin_port=htons(6003);//端口号,主机转网络专用
seraddr.sin_addr.s_addr=inet_addr(SERADDR);//点分十进制转网络16进制(大端),设置IP
ret=bind(sockfd,(const struct sockaddr *)&seraddr,sizeof(seraddr));
if(-1==ret)
{
perror("bind");//错误检测
return -1;
}
#define BACKLOG 100;//队列长度
ret=listen(sockfd,BACKLOG);//监听
if(-1==ret)
{
perror("listen");//错误检测
return -1;
}
struct sockaddr_in cliaddr={0};
clifd=accept(sockfd,(struct sockaddr *)&cliaddr,sizeof(cliaddr))//accept等待客户端连接 处于监听阻塞状态;
//(struct sockaddr *)&cliaddr获取到客户端IP;clifd返回连接成功的客户端申请fd
}
客户端部分
客户端部分
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(-1==sockfd)//错误检查
{
error("sockfd");//错误检测
return -1;
}
seraddr.sin_family=AF_INET;//IPV4
seraddr.sin_port=htons(6003);//端口号,主机转网络专用
seraddr.sin_addr.s_addr=inet_addr(SERADDR);//点分十进制转网络16进制(大端),设置IP
ret=bind(sockfd,(const struct sockaddr *)&seraddr,sizeof(seraddr));
if(-1==ret)
{
error("bind");//错误检测
return -1;
}
connect成功后server端的accept不再阻塞,返回值为一个fd,会为这个连接新申请一个关于这个连接的fd,进行读写操作。
socket返回的fd为监听描述符,用于监听客户端,不能用于和任何客户端进行读写,accept返回的为连接上的客户端fd。
cliebt发送:
在建立连接后就可发送与接收。
char sendbuf[100]={0};
strcopy(sendbuf,"xxx");
ret=send(sockfd,sendbuf,strlen(sendbuf),0);
server接收
ret=recv(clifd,recvbuf,sizeof(sendbuf),0);
ret为成功接收的个数;clifd连接客户端的fd;recvbuf为接收到的字符;0为flag,默认为0
client多次发送
while(1)
{
scanf("%s",sendbuf);
ret=send(sockfd,sendbuf,strlen(sendbuf),0);
}
server多次接收
while(1)
{
ret=recv(clifd,recvbuf,sizeof(recvbuf),0);
memset(recvbuf,0,sizeof(recvbuf));//清理一下接收部分
}
bind:Address already in use 地址被占用看,原因异常中断关闭程序。修改方式等下一次运行;或者改一下端口号。
recv收消息,send发送消息。
原则上c/s是要做配合的,cli发送ser收,ser发cli收。①c/s是异步的,不知道下一步是发送还是接收。②方案依靠应用协议,双方做通信约定。
自定义协议:①规定link后主动向ser发出一个包,ser回应cli一个数据包,完成一次通信回合。②整个通信就由n个这样的回合完成。
ser:
{
①服务器接收
ret=recv(......);
memset(......);
②解析发出的数据包,开始
③干完活恢复客户端
ret=send(clifd,"ok",2,0);
//完成一个回合
}
cli:
{
①客户端发送
ret=send(......);
②客户端收服务器回答
memset(......);
ret=recv(......);
//完成一个回合
}
自定义数据包格式:
struct commu{
char name[20];
int age;
int cmd;
}info;
cli:
info st1;
①填充info
②发送info
ret=send(sockfd,&st1,sizeof(st1),0);
③等待ser回应
ret=recv(sockfd,......);
ser:
info st;
①收服务端发送消息
ret=recv(clifd,&st,sizeof(info),0);
②解析,干活
......
③回应客户端
ret=send(clifd,"ok",2,0);
常用应用层协议http,ftp等等。