Linux之网络编程(一)(参考代码)
https://blog.csdn.net/birdunderastarrysky/article/details/90631822
使用SELECT实现即时聊天小程序
目录
提问:
基本常识:
英文缩写与全称:
Linux相关指令:
1.什么是网络编程?为什么需要网络编程?
2.c语言提供了哪些结构体和接口?如何使用?
通用socket地址结构 struct sockaddr:
主机(大小端)向网络字节序(大端)的整数转换:
地址格式转化
域名和IP地址转化:
socket编程接口(系统调用):
UDP接收和发送接口:
其他:
3.TCP和UDP的区别?
网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的。
本地进程间通信有很多种,比如传递消息:管道、FIFO、消息队列同步:信号量共享内存但是我们使用这些很难去定位网络中某一台主机的某一个进程,这也是我们需要新的方法的原因。网络层的IP地址可以唯一的标识网络中的主机,而传输层的端口号又可以唯一标识一个进程,再加上相应的协议,我们构成了一个三元组(IP地址,协议,端口)来定位网络中某一台主机的某一个进程,因此我们便可以在网络中进行通信了。
第一个被广泛接受的socket API实现于1983年。实际上这组API已经被移植到了所有UNIX实现以及其他大多数操作系统上了。socket API是在POSIX.1g中进行正式规定,它作为标准草案在经历了10年之后于2000年被正式认可。现在它已经被SUSv3所取代了。
TCP服务器端:
read()/write()也是可以使用的,等价于将flags参数指定为0 。 服务器 socket() -> bind() -> listen() -> accept()(阻塞) -> recv()/send() -> close() 客户端 socket() --------------------------> connect() | -> recv()/send() ->close() int socket(int domain, int type, int protocol); 生成一个套接口描述符。 成功返回套接口描述符,失败返回-1 。 参数domain : AF_INET 允许通过IPV4协议网络连接起来的主机上的应用程序之间通信 AF_INET6 允许通过IPV6协议网络连接起来的主机上的应用程序之间通信 AF_UNIX或AF_LOCAL是同义词,允许同一主机上的应用程序通信。 参数type : SOCK_STREAM TCP协议 SOCK_DGRAM UDP协议 从Linux 内核2.6.27开始,允许两个非标准的标记与socket类型取OR。 SOCK_CLOEXEC 导致内核为新文件描述符启用close-on-exec标记(FD_CLOEXEC)。 SOCK_NONBLOCK 导致内核在底层打开着文件描述符上设置O_NONBLOCK标记。 参数protocol : 指定socket所使用的传输协议编号,通常为0 。int bind(int sockfd, struct sockaddr* my_addr, int addrlen); 用来绑定一个端口和ip地址,使套接口与指定端口号和IP地址相关联。 成功返回0,失败返回-1 。 参数sockfd:套接字描述符 参数my_addr:使用下面的结构体,然后强制转换成(struct sockaddr*)。 参数addrlen:结构体长度 sizeof(struct sockaddr) 。 struct sockaddr_in{ unsigned short sin_family; //协议IPV4:AF_INET uint16_t sin_port; //使用的端口编号 struct in_addr sin_addr; //IP地址 unsigned char sin_zero[8]; //未使用 }; struct in_addr{ uint32_t s_addr; };
int listen(int sockfd, int backlog); 使服务器的这个端口和IP处于监听状态,等待网络中某一客户机的连接请求,如果客户端有连接请求,端口就会接受这个连接。 成功返回0,失败返回-1 参数sockfd: socket描述符 参数backlog: 指定同时能处理的最大连接要求,通常为10或者5。最大可设置为128。
int accept(int scokFd, struct sockaddr* addr, int *addrlen); accept函数接受一个连接时,会返回一个新的socket标识符,以后的数据传输和读取就要通过这个 新的socket编号来处理,原来的socket还能使用,继续监听其他客户机的连接请求。 成功返回新的socket文件描述符,失败返回-1 参数scokFd:socket文件描述符,监听端口 参数addr:结构体指针变量,系统会把远程主机的信息(地址,端口号)保存到这个指针所指的结构体中。 参数addrlen:为整形指针,表示结构体长度,告诉内核有多少空间用于返回的socket地址。 accept()系统调用会创建一个新的socket,并且正是这个新socket会与执行connect()的对等socket进行连接。accept()调用返回的函数结果是已连接的socket的文件描述符。监听socket(sockFd)会保持打开状态,并且可以被用来接受后续的连接。如果不关心对等的socket的地址,可以将后两位参数设置为NULL和0。(后面某个时刻可使用getpeername()系统调用来获取对端的地址)。 int recv(int sockfd, void* buf, int bufLen, unsigned int flags); 通过套接字接收远端主机传来的数据,并把数据存到由参数buf指向的内存空间。 成功返回实际接收到的字符数,失败返回-1 。如果另一边连接断开,recv返回0 。 参数sockfd:表示socket文件描述符 参数buf:表示缓冲区 参数bufLen:表示缓冲区大小 参数flags:通常为0 注意:另一端写端断开,文件描述符会一直可读,recv会一直读到'\0',返回值为0 。 所以可以通过判断recv返回值确定对端是否断开。 read也是一样的。
int send(int sockfd, void* buf, int bufLen, unsigned int flags); 通过套接字把参数buf指向的内存空间中的信息发送给远端主机。 成功返回实际传送出去的字符数,失败返回-1 。 参数sockfd:表示socket文件描述符 参数buf:表示缓冲区 参数bufLen:表示缓冲区大小 参数flags:通常为0 send一个不是socket的文件描述符,send返回-1,errno=88,描述为send: Socket operation on non-socket.send一个不存在的文件描述符,send返回-1,errno=9,描述为send: Bad file descriptor但是并不会导致程序崩溃。 int close(int fd); 释放不再需要的文件描述符,让数据写回磁盘,并释放该文件所占用的资源。 文件顺利关闭返回0,失败返回-1 。 参数fd:表示文件描述符。 int shutdown(int sockfd, int how); 实现半关闭。
TCP客户端:
先通过socket()系统调用得到socket文件描述符,然后执行connect即可。客户端端口号由connet()时操作系统随机分配,并直到进程结束,一直保留。 int connect(int sockfd, struct sockaddr* server_addr, socklen_t addrlen); 用来请求连接远程服务器。将参数sockfd的socket连接到server_addr指定的服务器IP和端口号上。 成功返回0,失败返回-1 。 参数sockfd是socket文件描述符。 参数server_addr是结构体,指定想要连接的地址(不是自己的)。(上面有) 参数addrlen是一个整形数,表示struct sockaddr的大小。int sockFd = socket(AF_INET, SOCK_DGRAM, 0); struct sockaddr_in serAddr; serAddr.sin_family = AF_INET; serAddr.sin_port=htons(atoi("8888")); serAddr.sin_addr.s_addr=inet_addr("192.168.1.111"); int ret;//接收返回值 ret = bind(sockFd, (struct sockaddr*)&serAddr, sizeof(struct sockaddr)); if(-1 == ret){ perror("bind"); return 0; } struct sockaddr_in clientAddr; //接收对端地址和端口号 bzero(&clientAddr,sizeof(clientAddr)); socklen_t len=sizeof(clientAddr); char buf[128]={0}; recvfrom(socketFd,buf,sizeof(buf),0,(struct sockaddr*)&clientAddr,&len);//接收对端信息
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); 通过套接字接收远端主机传来的数据,并把数据存到由参数buf指向的内存空间。 成功返回实际接收到的字符数,失败返回-1 。如果另一边连接断开,recv返回0 。 参数sockfd:表示socket文件描述符 参数buf:表示缓冲区 参数bufLen:表示缓冲区大小 参数flags:通常为0 参数src_addr:存放源主机的IP地址和端口号,不需要可以为NULL。 参数addrlen: src_addr的长度,不需要可以为NULL。 不管len参数是多大,recvfrom()只会从一个数据报socket中读取信息。如果消息大小超过了length字节,那么消息会被静默地截断为len字节。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); 通过套接字把参数buf指向的内存空间中的信息发送给远端主机。 成功返回实际传送出去的字符数,失败返回-1 。 参数sockfd:表示socket文件描述符 参数buf:表示缓冲区 参数bufLen:表示缓冲区大小 参数flags:通常为0 参数dest_addr和参数addrlen: 如果sendto用于面向连接的模式,这两个参数会被忽视。 如果用于UDP模式,两个参数指定了数据报发送到的socket。(也就是对端,不是自己)
TCP:也叫流socket,提供了一个可靠的双向的字节流通信信道
可靠传送不保留消息边界,字节流面向连接
UDP:也叫数据报socket,允许数据以数据报道的消息的形式进行交换。
不可靠传送,无序的,重复的,或者无法到达。保留消息边界不面向连接