-
套接字编程
2015-07-11 13:13:46+++++++套接字编程+++++++ (位于6-4的中间) 【socket( ); 创建套接字,返回一个套接字描述符】 int socket(int family, int type, int protocol); family:协议族 AF_INET: IPv4协议 AF_INET...+++++++套接字编程+++++++(位于6-4的中间)【socket( ); 创建套接字,返回一个套接字描述符】int socket(int family, int type, int protocol);family:协议族AF_INET: IPv4协议AF_INET6: IPv6协议AF_LOCAL: UNIX域协议FA_ROUTE: 路由套接字(socket)AF_KEY: 秘钥套接字(socket)type:套接字类型SOCK_STREAM: 字节流套接字(socket)SOCK_DGRAM: 数据报套接字(socket)SOCK_RAW: 原始套接字(socket)protoco:0<<成功:非负套接字描述符 出错:-1>>【bind( ); 为创建的套接字通信接口绑定IP和端口】int bind(int sock_fd, struct socdaddr *my_addr, int addrlen);sockfd:套接字描述符my_addr:本地地址addrlen:地址长度<<成功:0 出错:-1>>【listen( ); 设置套接字为监听状态】/*被动套接字,即套接字只能接受别的主机的请求,而不能主动连接别的主机*/int listen(int sock_fd, int backlog);sock_fd:套接字描述符backlog:在同一时刻能接受的连接请求个数(5-10)<<成功:0 出错:-1>>【accept( ); 接受连接请求】 服务端用/* 接受一个新的连接请求,并返回一个新的套接字,这个套接字代表一个新的连接 */int accept(int sock_fd, struct sockaddr *addr, socklen_t *addrlen);sock_fd:套接字描述符addr:用来保存发起连接请求的那一端的信息(ip和端口)。addrlen:保存结构体长度地址<<成功:非负套接字描述符 出错:-1>>【connect( ); 向服务器发起连接请求】/* 客户端来调用该函数,用来向服务器发起连接请求 */int connect(int sock_id, struct sockaddr *serv_sddr, int addrlen);aock_id:通过哪个套接字来发送请求serv_sttr:保存服务器的信息,也就是把连接请求发给ip和端口为指定的那个服务器addrlen:地址长度<<成功:0 出错:-1>>【设置套接字的属性】int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)level:指定控制套接字的层次.可以取三种值:1)SOL_SOCKET:通用套接字选项.2)IPPROTO_IP:IP选项.3)IPPROTO_TCP:TCP选项.optname:获得或者是设置套接字选项optval:为套接字设置的参数的地址optlen:为套接字设置的参数的地址长度//======实例==================【允许重复绑定IP和端口的函数,照写即可】int i = 1;setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));【设定套接字定时阻塞】struct timeval tv;tv.tv_sec = 5; //设置5秒时间tv.tv_usec = 0;setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); // 设置接收超时accept();==================================================================struct fd_set rdfs;struct timeval tv = {5 , 0}; // 设置5秒时间FD_ZERO(&rdfs);FD_SET(sockfd, &rdfs);if (select(sockfd+1, &rdfs, NULL, NULL, &tv) > 0) // socket就绪recv() / recvfrom() // 从socket读取数据==================================================================void handler(int signo) { return; }signal(SIGALRM, handler);alarm(5);if(recv(,,,) < 0) ……【心跳检测】为了维持服务器和客户端的之间的连接,客户端每隔一个固定的时间(1-255秒)向服务器发送一个心跳包,以表明客户端和软硬件运行正常,如果服务器在间隔时间内,没有收到心跳包,则认为客户端出现问题,可以根据相应情况进行处理。将二进制IP转换为字符串并返回字符串的地址char *inet_ntoa(sock_client.sin_addr);测试程序:建立一个服务器,另建立一个客户端登陆服务器,并向其发送数据,服务器端显示收到的数据步骤:服务器端:(server)No.1: 创建套接字并接收套接字描述符 socketNo.2: 配置服务器结构体信息 set:struct sockaddrNo.3: 绑定IP地址和端口到套接字 bindNo.4: 设置套接字为监听状态 listenNo.5: 接收客户端连接(阻塞)acceptNo.6: 读取数据并做相关的操作 read、recvNo.7: close(sock_fd);客户端:(client)No.1: 创建套接字并接受套接字描述符socketNo.2: 配置服务器结构体信息set:struct sockaddrNo.3: 向客户端发起连接请求 connectNo.4: 进行数据写入操作 wirte、sendNo.5: close(sock_fd);+++++++UDP+++++++(位于6-4的中间)【sendto( ); 】int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);sockfd:套接字描述符msg:指向要发送数据的指针len:数据长度flags:一般为0to:目的机的IP地址和端口号信息fromlen:地址长度<<成功:实际发送字节数 失败:-1>>【recvfrom( ); 】int recvfrom (int sockfd, void *buf, int len, unsigned int flags, const struct sockaddr *from, int fromlen);sockfd:套接字描述符buf:存放接收数据的缓冲区len:数据长度flags:一般为0from:源主机的IP地址和端口号信息fromlen:地址长度<<成功:实际收到的字节数 失败:-1>>>>>>以上两个函数只用于UDP协议Connect 和 accept函数间会发生三次握手(3个空包)第一次握手:client向server发送一个包(SYN包)TCP头内的SYN=1, seq=一个随机序列号x客户端处于SYN_SENT状态第二次握手:Server回复一个包给client(SYN+ACK包)TCP头内的SYN=1, ACK=1, seq= y, ack=x+1服务端处于SYNf_RCVD状态第三次握手:ACK包(ACK=1, ack=y+1)客户端和服务端都处于ESTABUSHED 状态,表示握手成功为什么TCP是面向对象的,因为它要进行三次握手四次握手Client向server发送一个请求关闭的包Server给client一个回复Server告诉client已经没有数据可发送了Client给server回复内核和驱动帮我们完成阻塞的阻塞I/O·非阻塞I/O+++++++I/O多路复用+++++++【lelect() 函数,实现自动监控文件描述符所对应的文件是否可写、可读......s】int select(int n, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout);n:表示最大的文件描述符的值加1read_fds:要关注的可读(有数据可读、有新的连接请求)的文件描述符的集合write_fds:要关注的可写的文件描述符的集合except_fds:要关注的异常的文件描述符的集合timeout:超时时间,如果在规定的时间内,没有任何文件描述符准备好,则select直接返回6//select调用一次,只能进行一次监控,如果要连续监控,则需使用循环//select返回之后,不满足条件的文件描述符对应的为都被清零,所以如果要重新检查,必须将fd重新加入集合返回值:<0:出错 ==0:超时 >0: 有文件描述符准备好(某些IO端口满足可读、可写或异常的条件)//select不会告诉用户具体是哪一个IO准备好,需要程员自己来判断【timeout结构体】struct timeval {long tv_sec; /*秒数*/long tv_usec; /*微秒数*/};在设置文件描述符时我们需要用到几个宏void FD_ZERO(fd_set *fdset);//把文件描述符集合中所有的位都清零void FD_SET(int fd,fd_set *fdset);//将文件描述符fd加入到集合fdset中void FD_CLR(int fd,fd_set *fdset);//将文件描述符从集合fdset中去除int FD_ISSET(int fd,fd_set *fdset);//判断描述符fd是否在集合fdset中,检测集合中fd对应的位是否为1select编程步骤:1:获得文件描述符int fd_serial = open("/dev/tty", O_RDWR);int fd_socket = socket(AF_INET, SOCK_STREAM, 0);bind(xxx);listen(xxxx);2:把所有关注的文件描述符加入到集合中fd_set fdset;FD_ZERO(&fdset);//对所有位进行清零FD_SET(fd_serial, &fdset);FD_SET(fd_socket, &fdset);3:调用select进行监控struct timeval tm = {5,0};max_fd = fd_serial>fd_socket?fd_serial:fd_socket;ret = select(max_fd +1 ,&fdset, NULL, NULL, &tm);4:根据返回情况进行判断if(ret < 0){perror("select");exit(1)}if(ret == 0)printf("说明在5秒之内,没有任何IO准备好,超时\n!");if(ret> 0) //有IO满足可读条件{if(FD_ISSET(fd_serial,&fdset))read(fd_serial, buf, 20);//串口有数据可读if(FD_ISSET(fd_socket,&fdset))conn_fd = accept(fd_socket, xxxxx); //有新的连接请求}其他相关函数:struct hostent *gethostbyname(const char *name); //根据主机名或域名获得主机信息struct hostent {char *h_name; /*官方主机名*/char **h_aliases; /*主机的别名列表*/--->char *h_aliases[ ]={“host1”, “host1”};int h_addrtype; /*主机的地址类型(IPv4 IPv6)*/int h_length; /*地址长度*/char **h_addr_list; /*主机的IP地址列表*/--->char *h_addr_list[ ] ={ip1, 1p2};本质:解析文件 /etc/hosts----------------------------------------------------------------------------------------------------------------测试程序:用select创建一个类似于server的模型编写步骤:Not1:创建一个链表并初始化;创建集合:fd_set read_fds; int max;No.2:创建套接字并使套接字处于监听状态No.3:先将max置为sock_fd的值,然后进入while(1)进行select循环监控while(1){FD_ZERO(&read_fds); //对”集合“所有位清零FD_SET(sock_fd, &read_fds); //将sock_fd加入”集合“if(!(is_empty(head)){p = head->next;遍历链表将将每个链表节点放入”集合“}ret=select(max_fd+1, &read_fds, NULL, NULL, NULL);//调用select开始监控ret<0:--->perrorif(FD_ISSET(sock_fd, &read_fds))Accept:进行客户端连接 并将client_fd放入链表if(!is_empty(head)){p=head37_->next; if(p!=NULL);if(FD_ISSET(p->client, &read_fds)); //如果某client有数据过来了,则集合中相应的位为1read(); 返回值为0说明客户端调用了close(sock_fd)关闭了套接字从链表中关闭文件描述符并关闭该文件否则输出接收到的内内容p=p->next;}}No.4:close(sock_fd);
-
套接字编程---2(TCP套接字编程的流程,TCP套接字编程中的接口函数,TCP套接字的实现,TCP套接字出现的问题...
2019-09-11 15:40:13TCP套接字编程中的接口 socket 函数 #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, int protocol); domain: AF_INET 这是大多数用来产生...TCP模型创建流程图
TCP套接字编程中的接口
socket 函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain:
- AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
- AF_INET6 与上面类似,不过是来用IPv6的地址
- AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
type:
- SOCK_STREAM 这个协议是按照顺序的、可靠的、 数据完整的基于字节流的连接。 这是一个使用最多的socket类型,这个socket 是使用TCP来进行传输。
- SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
- SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
- SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。 (ping、traceroute使用该协议)
- SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序
protocol:
传0 表示使用默认协议。
返回值
成功:返回指向新创建的socket的文件描述符,失败:返回-1,设置errno
socket函数的作用
socket()打开一个网络通讯端口,如果成功的话,就像 open()一样返回一个文件描述符,应用程序可以像读写文件一样用 read/write 在网络上收发数据,如果 socket()调用出错则返回-1。对于 IPv4,domain 参数指定为 AF_INET。 对于 TCP 协议,type 参数指定为 SOCK_STREAM,表示面向流的传输协议。如果是 UDP 协议,则 type 参数指定为 SOCK_DGRAM,表示面向数据报的传输协议。protocol 参数的介绍从略,指定为 0 即可。
bind 函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:
socket文件描述符
addr:
构造出IP地址加端口号
addrlen:
sizeof(addr)长度 返回值: 成功返回0,失败返回-1, 设置errno
bind函数的作用
-
服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可 以向服务器发起连接,因此服务器需要调用 bind 绑定一个固定的网络地址和端口号。
-
**bind()的作用是将参数 sockfd 和 addr 绑定在一起,使 sockfd 这个用于网络通讯的文件描述符监听 addr 所描述 的地址和端口号。**前面讲过,
structsockaddr*
是一个通用指针类型,addr 参数实际上可以接受多种协议的 sockaddr 结构体,而它们的长度各不相同,所以需要第三个参数 addrlen 指定结构体的长度。如:struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(6666); -
首先将整个结构体清零,然后设置地址类型为
AF_INET
,网络地址为INADDR_ANY
,这个宏表示本地的任意 IP 地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个 IP 地址,这样设置可以在所有的 IP 地址上监听,直 到与某个客户端建立了连接时才确定下来到底用哪个 IP 地址,端口号为 6666。
accept 函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockdf:
socket文件描述符
addr:
传出参数,返回链接客户端地址信息,含IP地址和端口号
addrlen:
传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
返回值:
成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno
accpet函数的作用
三方握手完成后,服务器调用 accept()接受连接,如果服务器调用 accept()时还没有客户端的连接请求,就阻塞 等待直到有客户端连接上来。addr 是一个传出参数,accept()返回时传出客户端的地址和端口号。addrlen 参数是一 个传入传出参数(value-resultargument),传入的是调用者提供的缓冲区 addr 的长度以避免缓冲区溢出问题,传出 的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区) 。如果给 addr 参数传 NULL,表示不关心 客户端的地址。
示例
while (1) { cliaddr_len = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); n = read(connfd, buf, MAXLINE); ...... close(connfd); }
整个是一个 while 死循环,每次循环处理一个客户端连接。由于 cliaddr_len 是传入传出参数,每次调用 accept() 之前应该重新赋初值。accept()的参数 listenfd是先前的监听文件描述符,而 accept()的返回值是另外一个文件描述符 connfd,之后与客户端之间就通过这个 connfd 通讯,最后关闭 connfd断开连接,而不关闭 listenfd,再次回到循环 开头 listenfd 仍然用作 accept 的参数。accept()成功返回一个文件描述符,出错返回-1。
connect 函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockdf:
socket文件描述符
addr:
传入参数,指定服务器端地址信息,含IP地址和端口号
addrlen:
传入参数,传入sizeof(addr)大小
返回值:
成功返回0,失败返回-1,设置errno
客户端需要调用 connect()连接服务器,connect 和 bind 的参数形式一致,区别在于 bind 的参数是自己的地址, 而 connect 的参数是对方的地址。connect()成功返回 0,出错返回-1。C/S 模型-TCP
Tcp通信的实现
封装接口
tcpsocket.hpp
/* * 封装Tcpsocket类,向外提供更加轻便的tcp套接字接口 * 1.创建套接字 Socket() * 2.绑定地址信息 Bind(std::string &ip,uint16_t port) * 3.服务端开始监听 Listen(int backlog = 5) * 4.服务端获取已完成连接的客户端socket Accept(TcpSocket &clisock,std::string &cli_ip,uint16_t port) * 5.接受数据 Rec(std::string &buf) * 6.发送数据 Send(std::string &buf) * 7.关闭套接字 Close() * 8.客户端向服务器发起连接请求 Connect(std::string &srv_ip,uint16_t srv_port) */ #include<iostream> #include<string> #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<errno.h> #include<netinet/in.h> #include<arpa/inet.h> #include<sys/socket.h> #define CHECK_RET(q) if((q) == false){return -1;} class TcpSocket { public: TcpSocket(){ } ~TcpSocket(){ } bool Socket(){ _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(_sockfd < 0){ perror("socket error"); return false; } return true; } bool Bind(std::string &ip,uint16_t 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); int ret = bind(_sockfd, (struct sockaddr *)&addr ,len); if(ret < 0){ perror("bind error"); return false; } return true; } bool Listen(int backlog = 5){ //int listen (int sockfd,int backlog) //sockfd: 套接字描述符 //backlog:最大并发连接数--决定内核中已完成连接队列结点个数 //backlog决定的不是服务端能接受的客户端最大上限 int ret =listen(_sockfd,backlog); if(ret < 0){ perror("listen error"); return false; } return true; } bool Accept(TcpSocket &cli,std::string &cliip,uint16_t &port){ //int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen); //sockfd:套接字描述符 //addr: 新建连接的客户端地址信息 //addrlen: 新建客户端的地址信息长度 //返回值:返回新建客户端socket的描述符 struct sockaddr_in addr; socklen_t len = sizeof(struct sockaddr_in); int sockfd = accept(_sockfd,(sockaddr*)&addr,&len); if(sockfd < 0){ perror("accept error"); return false; } cli.SetFd(sockfd); cliip = inet_ntoa(addr.sin_addr); port = ntohs(addr.sin_port); return true; } void SetFd(int sockfd){ _sockfd = sockfd; } bool Connect(std::string &srv_ip,uint16_t srv_port){ //int connect(int sockfd,const struct sockaddr *adrr,socklen_t addrlen); //sockfd :套接字描述符 //addr:服务端地址信息 //addrlen:地址信息长度 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(struct sockaddr_in); int ret = connect(_sockfd,(struct sockaddr*)&addr,len); if(ret < 0){ perror("connect error"); return false; } return true; } bool Recv(std::string &buf){ //ssize_t recv(int sockfd,void *buf ,size_t len,int flags) //sockfd :服务端为新客户端新建的socket描述符 //flags: 0--默认阻塞接受 没有数据一直等待 // MSG_PEEK 接受数据,但是数据并不从缓冲区移除 // 常用于探测性数据接受 //返回值:实际接收字节长度;出错返回-1;若连接断开则返回0 //recv默认阻塞的,意味着没有数据则一直等,不会返回0 //返回0只有一种情况,就是连接断开,不能再继续通信了 char tmp[4096]; int ret = recv(_sockfd,tmp,4096,0); if(ret < 0){ perror("recv error"); return false; }else if(ret == 0){ printf("peer shutdown\n"); return false; } buf.assign(tmp,ret); return true; } bool Send(std::string &buf){ //ssize_t send (int sockfd,void *buf,size_t len,int flags) int ret = send(_sockfd,buf.c_str(),buf.size(),0); if(ret < 0){ perror("send error"); return false; } return true; } bool Close(){ close(_sockfd); return true; } private: int _sockfd; };
客户端实现
/* *基于封装的Tcpsocket实现tco客户端程序 1.创建套接字 2.为套接字绑定地址信息(不推荐用户手动绑定) while(1){ 4.发送数据 5.接收数据 } 6.关闭套接字 */ #include"tcpsocket.hpp" int main(int argc,char *argv[]){ if(argc != 3){ std::cout<<"./tco_cli 192.168.145.132 9000\n"; return -1; } std::string ip = argv[1]; uint16_t port = atoi(argv[2]); TcpSocket sock; CHECK_RET(sock.Socket()); CHECK_RET(sock.Connect(ip,port)); while(1){ std::string buf; std::cout<<"client say:"; fflush(stdout); std::cin >> buf; sock.Send(buf); buf.clear(); sock.Recv(buf); std::cout<<"server say:"<<buf<<std::endl; } sock.Close(); return 0; }
服务端的实现
/* *基于封装的tcpsocket,实现tcp服务端程序 1. 创建套接字 2.为套接字绑定地址信息 3.开始监听,如果有连接进来,自动完成三次握手 while(1){ 4,从已完成连接队列,获取新建的客户端连接socket 5.通过新建的客户端连接socket,与指定的客户端进行通信,recv 6.send } 7. 关闭套接字 */ #include"tcpsocket.hpp" int main(int argc,char *argv[]){ if(argc != 3){ std::cout<<"./tcp_srv 192.168.145.132 9000\n"; return -1; } std::string ip =argv[1]; uint16_t port =atoi(argv[2]); TcpSocket sock; CHECK_RET(sock.Socket()); CHECK_RET(sock.Bind(ip,port)); CHECK_RET(sock.Listen()); while(1){ TcpSocket clisock; std::string cliip; uint16_t cliport; if(sock.Accept(clisock,cliip,cliport) == false){ //当已完成的连接队列中没有socket,会阻塞 continue; } std::cout<<"new client:"<<cliip<<":"<<cliport<<std::endl; std::string buf; clisock.Recv(buf); std::cout<<"client say:"<<buf<<std::endl; buf.clear(); std::cout<<"server say:"; fflush(stdout); std::cin>>buf; clisock.Send(buf); } sock.Close(); }
程序出现的问题
这个实现的最基本的tcp服务端程序中,因为服务端不知道客户端数据什么时候到来,因此程序只能写死;但是写死就有可能会造成阻塞(accep/recv),导致服务端无法同时处理多个客户端的请求
多进程tcp服务端程/多线程版本tcp服务端程序
适用多进程tcp服务端程序的处理多客户端请求;每当一个客户端的连接到来,都创建一个新的子进程,让子进程单独与客户端进行通信;这样的话父进程永远只处理新连接
TCP套接字多进程版本
#include<signal.h> #include"tcpsocket.hpp" #include<sys/wait.h> void sigcb(int no){ while(waitpid(-1,NULL,WNOHANG) > 0); } int main(int argc,char *argv[]){ if(argc != 3){ std::cout<<"./tcp_srv 192.168.145.132 9000\n"; return -1; } std::string ip =argv[1]; uint16_t port =atoi(argv[2]); signal(SIGCHLD,sigcb); TcpSocket sock; CHECK_RET(sock.Socket()); CHECK_RET(sock.Bind(ip,port)); CHECK_RET(sock.Listen()); while(1){ TcpSocket clisock; std::string cliip; uint16_t cliport; if(sock.Accept(clisock,cliip,cliport) == false){ //当已完成的连接队列中没有socket,会阻塞 continue; } std::cout<<"new client:"<<cliip<<":"<<cliport<<std::endl; int pid =fork(); if(pid == 0){ //子进程专门处理每个客户端的通信 while(1){ std::string buf; clisock.Recv(buf); std::cout<<"client say:"<<buf<<std::endl; buf.clear(); std::cout<<"server say:"; fflush(stdout); std::cin>>buf; clisock.Send(buf); } clisock.Close(); exit(0); } //父进程关闭套接字,因为父子进程数据独有 //不关闭,会造成资源泄露 clisock.Close();//父进程不通信 } sock.Close(); }
TCP套接字多线程版本
#include"tcpsocket.hpp" #include<pthread.h> void* thr_start(void *arg){ TcpSocket *clisock = (TcpSocket *)arg; while(1){ //因为线程之间,共享文件描述符表,因此在一个线程中打开的文件 //另一个线程只要能够获取文件描述符,就能在操作文件 std::string buf; clisock->Recv(buf); std::cout<<"client say:"<<buf<<std::endl; buf.clear(); std::cout<<"server say:"; fflush(stdout); std::cin>>buf; clisock->Send(buf); } clisock->Close(); delete clisock; return NULL; } int main(int argc,char *argv[]){ if(argc != 3){ std::cout<<"./tcp_srv 192.168.145.132 9000\n"; return -1; } std::string ip =argv[1]; uint16_t port =atoi(argv[2]); TcpSocket sock; CHECK_RET(sock.Socket()); CHECK_RET(sock.Bind(ip,port)); CHECK_RET(sock.Listen()); while(1){ TcpSocket *clisock = new TcpSocket(); std::string cliip; uint16_t cliport; if(sock.Accept(*clisock,cliip,cliport) == false){ //当已完成的连接队列中没有socket,会阻塞 continue; } std::cout<<"new client:"<<cliip<<":"<<cliport<<std::endl; pthread_t tid; pthread_create(&tid,NULL,thr_start,(void *)clisock); pthread_detach(tid); } sock.Close(); }
tcp连接断开
tcp自己实现了保活机制:当长时间没有数据通信,服务端会向客户端发送保活探测包;当这些保活探测包连续多次都没有响应,则认为连接断开
recv返回0;send触发异常 -
UDP 套接字编程
2019-01-23 09:05:48UDP 套接字编程 UDP 套接字编程相较于TCP 套接字编程会简单一些, UDP 仅提供无连接的不可靠数据报协议,而TCP是面向连接的字节流协议. UDP 函数介绍 这里, 服务器和客户端都不需要调用 TCP 的 connect 方法, 客户端...UDP 套接字编程
UDP 套接字编程相较于TCP 套接字编程会简单一些, UDP 仅提供无连接的不可靠数据报协议,而TCP是面向连接的字节流协议.
UDP 函数介绍
这里, 服务器和客户端都不需要调用 TCP 的 connect 方法, 客户端可以直接发送数据, 而服务端仅在为套接字命名(绑定端口)之后, 就可以接收消息. 函数介绍如下:
-
socket 创建一个套接字并返回该套接字的文件描述符。
int socket(int domain, int type, int protocol);
-
套接字命名
为套接字关联一个 IP 地址和端口号int bind(int socket, const struct sockaddr *address, size_t address_len);
-
等待客户数据到达(阻塞)
服务器不接收来自客户的连接,只需使用 recvfrom 系统调用,等待客户的数据到达。
int recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *src_addr, socklen_t *src_len);
-
客户端发送数据
不需要建立连接, 客户只需调用 sendto 系统调用向服务器发送消息:
int sendto(int socket, const void *buffer, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len);
-
关闭套接字
操作系统为每个套接字分配了一个文件描述符,使用 close 系统调用通知操作系统回收文件描述符,可以:
close(fd);
UDP 程序示例
- 服务端
#include "unp.h" int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr, cliaddr; sockfd = Socket(AF_INET, SOCK_DGRAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(sockfd, (SA *) &servaddr, sizeof(servaddr)); dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr)); } void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen) { int n; socklen_t len; char mesg[MAXLINE]; for ( ; ; ) { len = clilen; n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len); Sendto(sockfd, mesg, n, 0, pcliaddr, len); } }
- 客户端
#include "unp.h" int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if (argc != 2) err_quit("usage: udpcli <IPaddress>"); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); Inet_pton(AF_INET, argv[1], &servaddr.sin_addr); sockfd = Socket(AF_INET, SOCK_DGRAM, 0); dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr)); exit(0); } void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) { int n; char sendline[MAXLINE], recvline[MAXLINE + 1]; while (Fgets(sendline, MAXLINE, fp) != NULL) { Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen); n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); recvline[n] = 0; Fputs(recvline, stdout); } }
-
-
Python套接字编程(tcp的socket, 流式套接字)
2019-12-05 17:12:53Python实现套接字编程:import socket 套接字分类 流式套接字(SOCK_STREAM): 以字节流方式传输数据,实现tcp网络传输方案。(面向连接–tcp协议–可靠的–流式套接字) 数据报套接字(SOCK_DGRAM):以数据报形式传输数据,...1. 套接字介绍
- 套接字 : 实现网络编程进行数据传输的一种技术手段
- Python实现套接字编程:import socket
- 套接字分类
流式套接字(SOCK_STREAM): 以字节流方式传输数据,实现tcp网络传输方案。(面向连接–tcp协议–可靠的–流式套接字)
数据报套接字(SOCK_DGRAM):以数据报形式传输数据,实现udp网络传输方案。(无连接–udp协议–不可靠–数据报套接字)
2. tcp套接字编程
一 服务端流程
socket---->bind---->listen---->accept---->send/recv---->close
- 创建套接字
sockfd=socket.socket(socket_family=AF_INET,socket_type=SOCK_STREAM,proto=0)
功能:创建套接字
参数: socket_family 网络地址类型, AF_INET表示ipv4
socket_type 套接字类型:
SOCK_STREAM(流式)
SOCK_DGRAM(数据报)
proto 通常为0 选择子协议
返回值: 套接字对象 - 绑定地址
本地地址 : ‘localhost’ , ‘127.0.0.1’
网络地址 : ‘172.40.91.185’
自动获取地址: ‘0.0.0.0’
sockfd.bind(addr)
功能: 绑定本机网络地址
参数: 二元元组 (ip,port)
pythonNet
(‘0.0.0.0’,8888) - 设置监听
sockfd.listen(n)
功能 : 将套接字设置为监听套接字,确定监听队列大小
参数 : 监听队列大小 - 等待处理客户端连接请求
connfd,addr = sockfd.accept()
功能: 阻塞等待处理客户端请求
返回值:
connfd 客户端连接套接字
addr 连接的客户端地址 - 消息收发
data = connfd.recv(buffersize)
功能 : 接受客户端消息
参数 :每次最多接收消息的大小
返回值: 接收到的内容
n = connfd.send(data)
功能 : 发送消息
参数 :要发送的内容 bytes格式
返回值: 发送的字节数 - 关闭套接字
sockfd.close()
功能:关闭套接字
""" tcp_server.py 服务端 """ import socket # 创建tcp套接字 sockfd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定地址 sockfd.bind(('0.0.0.0',8888)) # 设置监听 sockfd.listen(3) # 处理客户端链接 print("Waiting for connect...") connfd,addr = sockfd.accept() print("Connect from",addr) # 收发消息 data = connfd.recv(1024) print("Recv:",data) n = connfd.send(b"OK\n") # 发送字节串 # 关闭套接字 connfd.close() sockfd.close() # 客户端测试命令 telnet 127.0.0.1 8888
tcp服务端的练习
""" 练习 : 选择一张图片,从客户端上传到服务端 温馨提示: 客户端读取图片的内容 将内容发送给服务端 服务端接受图片内容 保存在服务端某个位置 """ from socket import * from time import localtime # 存在这里 SAVE_PATH = "/home/tarena/图片/" s = socket() s.bind(('127.0.0.1',8888)) s.listen(3) c,addr = s.accept() print("Connect from",addr) # 打开文件 tmp = localtime() img = "%s-%s-%s.jpg"%tmp[:3] f = open(SAVE_PATH+img,'wb') #接受图片 while True: data = c.recv(1024) if not data: break f.write(data) f.close() c.close() s.close()
客户端流程
socket----> connect ----> send/recv ----> close
- 创建套接字
注意:只有相同类型的套接字才能进行通信 - 请求连接
sockfd.connect(server_addr)
功能:连接服务器
参数:元组 服务器地址 - 收发消息
注意: 防止两端都阻塞,recv send要配合 - 关闭套接字
tcp 套接字数据传输特点
tcp连接中当一端退出,另一端如果阻塞在recv,此时recv会立即返回一个空字串。
tcp连接中如果一端已经不存在,仍然试图通过send发送则会产生BrokenPipeError
一个监听套接字可以同时连接多个客户端,也能够重复被连接网络收发缓冲区
- 网络缓冲区有效的协调了消息的收发速度
- send和recv实际是向缓冲区发送接收消息,当缓冲区不为空recv就不会阻塞。
tcp粘包
原因:tcp以字节流方式传输,没有消息边界。多次发送的消息被一次接收,此时就会形成粘包。
影响:如果每次发送内容是一个独立的含义,需要接收端独立解析此时粘包会有影响。
处理方法- 人为的添加消息边界
- 控制发送速度
""" tcp_client.py tcp客户端 """ from socket import * # 创建tcp套接字 sockfd = socket() # 默认参数--》 tcp # 连接服务器 server_addr = ('127.0.0.1',8887) # 服务端地址 sockfd.connect(server_addr) while True: data = input("Msg>>") if not data: break # 发送给服务端 sockfd.send(data.encode()) # 转换为字节串 # if data == '##': # break data = sockfd.recv(1024) print("Server:",data.decode()) # 打印字符串 # 关闭套接字 sockfd.close()
-
Python 套接字编程
2018-11-13 13:00:11Python 套接字编程学习历程 1.什么是socket? Socket中文译作:套接字,socket是来建立‘通信’的基础,建立连接,传输数据‘通信端点’。 每一个套接字就是一组接口与端口的组合,用来发送或者接受信息。 socket... -
Linux网络编程——原始套接字编程
2015-03-27 17:47:16原始套接字编程和之前的 UDP 编程差不多,无非就是创建一个套接字后,通过这个套接字接收数据或者发送数据。区别在于,原始套接字可以自行组装数据包(伪装本地 IP,本地 MAC),可以接收本机网卡上所有的数据帧... -
Socket套接字编程
2018-09-18 17:57:48前天面试了环信公司,在...(1)基于UDP协议的socket套接字编程 UDP协议是非链接的协议,它不与对方建立连接,而是直接把要发送的数据发送给对方。所以UDP协议适用于一次传输数据量很少,对可靠性要求不高的应用场... -
套接字编程简介
2016-03-16 21:45:27整理自《Unix网络编程卷一》第三章 套接字通信主要目的是实现数据的交互,但是为了实现数据的交互我们通常还有许多工作要做。...+ 数据的发送、接收函数套接字地址结构 大多数套接字编程主要用于网络通信(Uni -
socket套接字编程(一)
2020-02-22 16:30:06Python实现套接字编程:import socket 套接字分类: 流式套接字(SOCK_STREAM):以字节流方式传输数据,实现tcp网络传输方案。(面向连接–tcp协议–可靠的–流式套接字) 数据报套接字(SOCK_DGRAM):以数据报... -
【Linux网络编程】原始套接字编程
2016-08-30 22:19:39原始套接字编程和之前的 UDP 编程差不多,无非就是创建一个套接字后,通过这个套接字接收数据或者发送数据。区别在于,原始套接字可以自行组装数据包(伪装本地 IP,本地 MAC),可以接收本机网卡上所有的数据帧... -
网络编程五数据报套接字编程
2019-02-28 15:32:281.与流式套接字编程相比不同的函数: sendto():数据报套接字描述符;要发送的字节序列的缓冲区;发送缓冲区的长度;套接字调用的方式;一个指向sockaddr结构的指针,保存服务器的端口号和IP地址;指定地址结构的... -
VS2015套接字编程断开套接字连接
2019-04-25 10:10:40断开已连接的套接字主要分为三个步骤:首先关闭套接字的发送和接收数据功能,接下来关闭套接字,最后释放Winsock动态库资源。 1 关闭套接字发送和接收数据功能 通过shutdown()函数关闭套接字的发送和接收数据的... -
网络编程>UDP套接字编程
2019-04-21 14:45:00UDP套接字编程 典型的UDP客户/服务器程序的函数调用如下: 1、缓冲区 发送缓冲区用虚线表示,任何UDP套接字都有发送缓冲区,不过该缓冲区仅能表示写到该套接字的UDP数据报的上限。如果应用进程写一... -
tcp套接字编程模型
2016-05-15 11:30:48tcp套接字编程模型。 -
TCP套接字编程(C语言)
2018-06-07 17:23:16本篇主要内容介绍:介绍TCP套接字基本概念介绍TCP套接字编程流程基本TCP套接字函数介绍1、TCP套接字基本概念(非官方解释,个人总结) 套接字是一种网络API,提供一种进程间的通信方法,使得相同主机或者不同主机上... -
PHP套接字编程
2012-05-28 14:37:45套接字编程,一般使用c或c++。特别的在web应用程序开发中,常用perl实现套接字。除此以外,用php进行套接字编程也是一个选择。Php可以胜任吗?当然可以。Php是一门高质量的web应用程序开发语言,他的许多特性可以... -
TCP套接字编程
2017-07-19 00:55:58TCP套接字编程socket函数为了执行网络I/O,一个进程必须做的第一件事情就是调用socket函数,指定期望的通信协议类型。#include <sys/socket.h>int socket(int family, int type, int protocol);返回:若成功则为非负... -
套接字编程常用函数
2016-03-16 21:46:23不同协议的套接字编程(TCP套接字、UDP套接字、原始套接字等)的模型有所差异,但一般会使用到一下常用函数: #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, ... -
Linux的并发套接字编程
2016-03-24 18:25:54近期学习了并发套接字编程,通过查找资料发现,并发套接字编程可以通过三种方式来实现: 1.多线程 2.多进程 3.多路复用 在上一篇博客中,我介绍了简单的socket模型,并且利用多进程实现了并发套接字编程。但是,我... -
Linux网络编程:原始套接字编程及实例分析
2017-11-30 15:23:12Linux网络编程:原始套接字编程及实例分析 转载 2016年07月29日 11:25:11 标签: socket / 网络编程 / 编程 / linux / 888 编辑 删除 Linux网络编程:原始套接字编程及实例分析 一、原始套接字能干什么? ... -
java_关于网络编程、套接字编程
2018-09-02 14:46:20套接字编程 ServerSocket类的使用 构造器: 常用方法: Socket类的使用 构造器: 常用方法: 网络编程: java语言中,提供了一套统一的编程接口,很多细节已经底层化,所以可以无痛的网络通信编程 提供... -
Python 网络编程(套接字编程)
2017-09-09 11:58:36Python 网络编程(套接字编程),python socket实现服务端与客户端的简单通信,socket编程模拟ssh的部分功能以及自动应答 -
基本TCP套接字编程
2019-03-11 12:14:20基本TCP套接字编程 connect函数 #include<sys/socket.h> int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen) 返回:若成功则为0,若出错则为-1 客户端调用...
-
动态编程系列 Dynamic Programming - Maximum Subarray
-
转行做IT-第8章 类与对象、封装、构造方法
-
Toshiba BlueTooth.zip
-
转行做IT-第6章 IDEA、方法
-
正确小程序全开源独立版.zip
-
git 入门
-
LoRaMac-node-3.4.1.zip
-
转行做IT-第2章 HTML入门及高级应用
-
四个推销技巧 销售人员一定要识
-
C++学习(一九四)QT5.15以后不再提供离线安装版
-
商业的本质——杰克·韦尔奇著
-
计算机网络基础
-
电商设计专业思维
-
30个生涯锦囊,带你跳出迷茫,找到适合你的职业方向
-
红蜘蛛 RSpider.zip
-
【数据分析-随到随学】SPSS调查问卷统计分析
-
excel批量生成word.rar
-
【2021】UI自动化测试框架(Selenium3)
-
golang内存对齐
-
httpclient支持jar打包