学习Linux网络编程基本函数
1,创建套接字socket
函数原型:
#include<sys/types.h> #include<sys/socket.h> int socket(int domain, int type, int protocol);
参数列表:
domain参数有以下这些值
AF_INET:IPv4协议
AF_INET6:IPv6协议
AF_LOCAL:Unix域协议
AF_ROUTE:路由套接口
AF_KEY:密钥套接口
type的值:
SOCKET_STREAM:双向可靠数据流,对应TCPSOCKET_DGRAM:双向不可靠数据报,对应UDPSOCKET_RAW:提供传输层以下的协议,可以访问内部网络接口,例如接收和发送ICMP报文
protocol得值:
type为SOCKET_RAW时需要设置此值说明协议类型,其他类型设置为0即可
函数的作用是创建一个指定格式的套接字并返回其描述符,成功返回描述符,失败返回-1;
2,绑定套接字bind
函数原型:
#include<sys/types.h> #include<sys/socket.h> int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
参数列表:
sockfd
为之前创建的套接字描述符
my_addr
是一个通用套接字结构体指针,在做tcp协议编程时通常使用sockaddr_in
结构体
该结构体内容如下;
struct socketaddr_in { unsigned short int sin_family;//对应地址族IP v4填AF_INTE uint16_t sin_port;//对应端口号 struct in_addr sin_addr;//对应ip地址 unsigned char sin_zero[8]; }; struct in_addr { uint32_t s_addr; };
addrlen为该上述结构体的大小,可以用sizeof求得;
在使用bind函数前需要先创建一个sockaddr_in类型的结构体,将服务器的信息保存到结构体中,
然后将创建的套接字与之绑定;成功返回0,失败返回-1;
在设置端口号和IP时先将结构体清空,如果是主函数传参,那么对应的端口号和ip都是字符串格式,
需要用函数转换,转换格式如下:
char port[]="8888" char ip[]="192.168.1.1" struct sockaddr_in seraddr' seraddr.sin_port=htos(atoi(port)) seraddr.sin_addr.s_addr=inet_addr(ip);
3,创建监听;listen
函数原型:
int listen(int fd, int backlog);
参数列表:
fd为要监听的套接字描述符;backlog为监听队列的大小;
(1) 执行listen 之后套接字进入被动模式。
(2) 队列满了以后,将拒绝新的连接请求。客户端将出现连接D 错误WSAECONNREFUSED。
(3) 在正在listen的套接字上执行listen不起作用。
4,等待连接accept
函数原型:
#include <sys/socket.h> int accept(int s, struct sockaddr * addr, int * addrlen);
对比bind函数可以发现两者的参数几乎一样,但是accept中的addr不被const修饰,
也就是说addr是用来保存连接的客户端的地址信息的,同杨addlen时返回的addr的大小;
所以accept函数的作用就是返回已连接的客户端的文件描述符,
并将客户端的地址信息保存在一个新的sockaddr_in结构体中;链接失败返回-1;
5, 收发消息send和recv
函数原型:
int send( SOCKET s, const char FAR *buf, int len, int flags ); int recv( SOCKET s, char FAR *buf, int len, int flags);
该函数的参数:
- 第一个参数指定发送/接受端套接字描述符;
- 第二个参数指明一个存放应用程序要发送数据的缓冲区;
- 第三个参数指明实际要发送/接收的数据的字节数;
- 第四个参数一般置0。
send的流程:
这里只描述同步Socket的send函数的执行流程。
当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲的长度,
- 如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;
- 如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,
- 如果是就等待协议把数据发送完,
- 如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len,
- 如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完,
- 如果len小于剩余空间大小send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里);
- 如果send函数copy数据成功,就返回实际copy的字节数,
- 如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;
- 如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。
要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。
- 如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。
- (每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,
- 如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)。
recv的流程:
这里只描述同步Socket的recv函数的执行流程。
当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,
- 如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,
- 如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,
- 如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,只到协议把数据接收完毕。
- 当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中
(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。
recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。
- 如果recv在copy时出错,那么它返回SOCKET_ERROR;
- 如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
- tcp协议本身是可靠的,并不等于应用程序用tcp发送数据就一定是可靠的.
- 不管是否阻塞,send发送的大小,并不代表对端recv到多少的数据.
- 在阻塞模式下, send函数的过程是将应用程序请求发送的数据拷贝到发送缓存中发送并得到确认后再返回.
但由于发送缓存的存在,表现为:如果发送缓存大小比请求发送的大小要大,那么send函数立即返回,同时向网络中发送数据;
否则,send向网络发送缓存中不能容纳的那部分数据,并等待对端确认后再返回(接收端只要将数据收到接收缓存中,
就会确认,并不一定要等待应用程序调用recv);
- 在非阻塞模式下,send函数的过程仅仅是将数据拷贝到协议栈的缓存区而已,
- 如果缓存区可用空间不够,则尽能力的拷贝,
- 返回成功拷贝的大小;如缓存区可用空间为0,则返回-1,同时设置errno为EAGAIN.
5,关闭套接字描述符close
函数:
close(sockfd);
和文件操作一样,套接字也是一个文件,使用完之后要关闭;
6,基于tcp协议的C/S服务器模型
图解tcp模型
7,实现代码
服务端:
#include <stdio.h> #include <stdlib.h> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <unistd.h> typedef struct sockaddr_in SIN; typedef struct sockaddr SA; int main(int argc,char *argv[]) { SIN seraddr; SIN cliaddr; int len=sizeof(SIN); //创建监听套接字 int lisfd=socket(AF_INET,SOCK_STREAM,0); if(lisfd<0) { perror("socket"); exit(0); } printf("创建套接字%d成功\n",lisfd); bzero(&seraddr,sizeof(seraddr)); seraddr.sin_family=AF_INET; seraddr.sin_port=htons(8888); seraddr.sin_addr.s_addr=inet_addr("192.168.1.6"); //绑定套接子 int ret=bind(lisfd,(SA*)(&seraddr),len); if(ret<0) { perror("bind"); exit(0); } printf("绑定成功\n"); //开始监听 ret=listen(lisfd,1024); if(ret<0) { perror("listen"); exit(0); } printf("监听成功\n"); //等待连接,将连接的套接字信息保存 int clifd=accept(lisfd,(SA*)(&cliaddr),(socklen_t *)(&len)); if(clifd<0) { perror("accept"); exit(0); } printf("客户端%d连接成功\n",clifd); //读写 char readbuf[1024]={0}; char sendbuf[1024]={0}; while(1) { recv(clifd,readbuf,sizeof(readbuf),0); printf("recv:%s\n",readbuf); fgets(sendbuf,sizeof(sendbuf),stdin); send(clifd,sendbuf,sizeof(sendbuf),0); } //关闭套接字 close(clifd); close(lisfd); return 0; }
客户端:
#include <stdio.h> #include <stdlib.h> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <unistd.h> typedef struct sockaddr_in SIN; typedef struct sockaddr SA; int main(int argc,char *argv[]) { SIN seraddr; //创建监听套接字 int serfd=socket(AF_INET,SOCK_STREAM,0); if(serfd<0) { perror("socket"); exit(0); } printf("创建套接字%d成功\n",serfd); bzero(&seraddr,sizeof(seraddr)); seraddr.sin_family=AF_INET; seraddr.sin_port=htons(8888); seraddr.sin_addr.s_addr=inet_addr("192.168.1.6"); //请求连接 int ret=connect(serfd,(SA*)(&seraddr),sizeof(SIN)); if(ret==-1) { perror("connect"); exit(0); } printf("连接成功\n"); //读写 char senbuf[1024]={0}; char readbuf[1024]={0}; while(1) { fgets(senbuf,sizeof(senbuf),stdin); send(serfd,senbuf,sizeof(senbuf),0); recv(serfd,readbuf,sizeof(readbuf),0); printf("recv:%s\n",readbuf); } //关闭套接字 close(serfd); return 0; }