精华内容
下载资源
问答
  • 套接字编程

    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: 创建套接字并接收套接字描述符 socket
    No.2: 配置服务器结构体信息 set:struct sockaddr
    No.3: 绑定IP地址和端口到套接字 bind
    No.4: 设置套接字为监听状态 listen
    No.5: 接收客户端连接(阻塞)accept
    No.6: 读取数据并做相关的操作 read、recv
    No.7: close(sock_fd);

    客户端:(client)
    No.1: 创建套接字并接受套接字描述符socket
    No.2: 配置服务器结构体信息set:struct sockaddr
    No.3: 向客户端发起连接请求 connect
    No.4: 进行数据写入操作 wirte、send
    No.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:一般为0
      to:目的机的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:一般为0
      from:源主机的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:表示最大的文件描述符的值加1
    read_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对应的位是否为1
     
    select编程步骤:
    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:--->perror
    if(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有数据过来了,则集合中相应的位为1
    read(); 返回值为0说明客户端调用了close(sock_fd)关闭了套接字
    从链表中关闭文件描述符并关闭该文件
            否则输出接收到的内内容
    p=p->next;
    }
    No.4:close(sock_fd);


    展开全文
  • TCP套接字编程中的接口 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:

    1. AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
    2. AF_INET6 与上面类似,不过是来用IPv6的地址
    3. AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用

    type:

    1. SOCK_STREAM 这个协议是按照顺序的、可靠的、 数据完整的基于字节流的连接。 这是一个使用最多的socket类型,这个socket 是使用TCP来进行传输。
    2. SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
    3. SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
    4. SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。 (ping、traceroute使用该协议)
    5. 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函数的作用

    1. 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可 以向服务器发起连接,因此服务器需要调用 bind 绑定一个固定的网络地址和端口号。

    2. **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);

    3. 首先将整个结构体清零,然后设置地址类型为 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触发异常

    展开全文
  • Python 套接字编程

    千次阅读 2018-11-13 13:00:11
    Python 套接字编程学习历程 1.什么是socket? Socket中文译作:套接字,socket是来建立‘通信’的基础,建立连接,传输数据‘通信端点’。 每一个套接字就是一组接口与端口的组合,用来发送或者接受信息。 socket...

    Python 套接字编程学习历程

    1.什么是socket?

    Socket中文译作:套接字,socket是来建立‘通信’的基础,建立连接,传输数据‘通信端点’。
    每一个套接字就是一组接口与端口的组合,用来发送或者接受信息。

    socket编程流程

    服务器端 客户端
    创建 socket 套接字 创建socket
    bind 绑定
    listen 监听
    accept 建立连接 connect
    接受请求连接 发送 send
    发送数据 接收recv

    如图:

    socket流程图
    一般的socket编程流程都可以按照上述流程模块来进行

    Tcp套接字编程

    # coding: utf-8
    #  服务器端代码
    from socket import *
    
    print ('我是服务端!')
    
    HOST = ''                 
    PORT = 50007
    BUFSIZ = 1024
    ADDR = (HOST,PORT) # 创建端口,规定缓冲区大小
    
    
    s = socket(AF_INET, SOCK_STREAM)  # 创建TCP socket对象
    s.bind(ADDR)  # 绑定地址
    s.listen(4)  # 监听TCP,4代表:操作系统可以挂起(未处理请求时等待状态)的最大连接数量。该值至少为1
    
    while 1:
       print("等待连接...")
       client, addr = s.accept()  # 开始被动接受TCP客户端的连接。
       print ('连接的地址', addr)
       
       while 1:
           data = client.recv(BUFSIZ).decode()  # 接受TCP数据 decode是由于此处接受bytes而不是 str类型
           print("接收到数据:",data)
           if not data: break
           
           client.send(data.encode())  # 把从客户端接收来的数据完整的,发送给客户端
       client.close()  
    s.close()
    
    

    客户端代码

    # coding: utf-8
    import socket
    
    print ('我是客户端!')
    
    HOST = 'localhost'    # 服务器的ip
    PORT = 50007              # 需要连接的服务器的端口
    BUFSIZ = 1024
    ADDR = (HOST,PORT)
    
    c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    c.connect(ADDR)
    
    
    while 1:
        data=input("请输入:\n >>>")
        if not data:
            break
        c.sendall(data.encode())  # 发送‘Hello,world’给服务器
        data = c.recv(BUFSIZ).decode()
        if not data:
            break
        print ('接收到', repr(data))  # 打印从服务器接收回来的数据
    c.close()
    

    本次在学习套接字的时候遇到 TypeError: a bytes-like object is required, not 'str'此类型的报错,网上查找资料显示该问题主要是由于当前操作的字符串是bytes类型的字符串对象,并对该bytes类型的字符串对象进行按照str类型的操作。

    解决方法

    转码成为str类型的或者是将对问题进行str类型操作转化为对bytes类型的操作。
    这里采用类型转换函数encode(),decode()

    字符可以通过encode()方法可以编码为指定的bytes
    反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes,
    要把bytes变为str,就需要用decode()方法
    

    这里把一些套接字的一些相关函数贴出来,希望大家熟悉一些相关函数

    s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。
    
    sk.listen(backlog)
    
      开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。
    
          backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5
      这个值不能无限大,因为要在内核中维护连接队列
    
    sk.setblocking(bool)
    
      是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。
    
    sk.accept()
    
     	接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。
    
      接收TCP 客户的连接(阻塞式)等待连接的到来
    
    s.connect(address)
    连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
    
    sk.connect_ex(address)
    
      同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061
    
    sk.close()
    
     	 关闭套接字
    
    sk.recv(bufsize[,flag])
    
      接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。
    
    sk.recvfrom(bufsize[.flag])
    
      与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
    
    sk.send(string[,flag])
    
      将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。
    
    sk.sendall(string[,flag])
    
      将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
    
          内部通过递归调用send,将所有内容发送出去。
    
    sk.sendto(string[,flag],address)
    
      将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。
    
    sk.settimeout(timeout)
    
    设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )
    
    sk.getpeername()
    
      返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
    
    sk.getsockname()
    
      返回套接字自己的地址。通常是一个元组(ipaddr,port)
    
    sk.fileno()
    
      套接字的文件描述符	
    

    初次学习python,先用比较简单的套接字来深入学习python,并学习一些简单的网络知识,心得确实有一点,python要学的好的话,还是要多敲代码

    展开全文
  • Python实现套接字编程:import socket 套接字分类 流式套接字(SOCK_STREAM): 以字节流方式传输数据,实现tcp网络传输方案。(面向连接–tcp协议–可靠的–流式套接字) 数据报套接字(SOCK_DGRAM):以数据报形式传输数据,...

    1. 套接字介绍

    1. 套接字 : 实现网络编程进行数据传输的一种技术手段
    2. Python实现套接字编程:import socket
    3. 套接字分类
      流式套接字(SOCK_STREAM): 以字节流方式传输数据,实现tcp网络传输方案。(面向连接–tcp协议–可靠的–流式套接字)
      数据报套接字(SOCK_DGRAM):以数据报形式传输数据,实现udp网络传输方案。(无连接–udp协议–不可靠–数据报套接字)

    2. tcp套接字编程

    一 服务端流程

    socket---->bind---->listen---->accept---->send/recv---->close

    1. 创建套接字
      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 选择子协议
      返回值: 套接字对象
    2. 绑定地址
      本地地址 : ‘localhost’ , ‘127.0.0.1’
      网络地址 : ‘172.40.91.185’
      自动获取地址: ‘0.0.0.0’
      sockfd.bind(addr)
      在这里插入图片描述
      功能: 绑定本机网络地址
      参数: 二元元组 (ip,port)
      pythonNet
      (‘0.0.0.0’,8888)
    3. 设置监听
      sockfd.listen(n)
      功能 : 将套接字设置为监听套接字,确定监听队列大小
      参数 : 监听队列大小
    4. 等待处理客户端连接请求
      connfd,addr = sockfd.accept()
      功能: 阻塞等待处理客户端请求
      返回值:
      connfd 客户端连接套接字
      addr 连接的客户端地址
    5. 消息收发
      data = connfd.recv(buffersize)
      功能 : 接受客户端消息
      参数 :每次最多接收消息的大小
      返回值: 接收到的内容
      n = connfd.send(data)
      功能 : 发送消息
      参数 :要发送的内容 bytes格式
      返回值: 发送的字节数
    6. 关闭套接字
      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

    1. 创建套接字
      注意:只有相同类型的套接字才能进行通信
    2. 请求连接
      sockfd.connect(server_addr)
      功能:连接服务器
      参数:元组 服务器地址
    3. 收发消息
      注意: 防止两端都阻塞,recv send要配合
    4. 关闭套接字

    tcp 套接字数据传输特点

    tcp连接中当一端退出,另一端如果阻塞在recv,此时recv会立即返回一个空字串
    tcp连接中如果一端已经不存在,仍然试图通过send发送则会产生BrokenPipeError
    一个监听套接字可以同时连接多个客户端,也能够重复被连接

    网络收发缓冲区

    1. 网络缓冲区有效的协调了消息的收发速度
    2. send和recv实际是向缓冲区发送接收消息,当缓冲区不为空recv就不会阻塞。

    tcp粘包

    原因:tcp以字节流方式传输,没有消息边界。多次发送的消息被一次接收,此时就会形成粘包。
    影响:如果每次发送内容是一个独立的含义,需要接收端独立解析此时粘包会有影响。
    处理方法

    1. 人为的添加消息边界
    2. 控制发送速度
    """
    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()
    
    展开全文
  • Linux网络编程——原始套接字编程

    万次阅读 多人点赞 2015-03-27 17:47:16
    原始套接字编程和之前的 UDP 编程差不多,无非就是创建一个套接字后,通过这个套接字接收数据或者发送数据。区别在于,原始套接字可以自行组装数据包(伪装本地 IP,本地 MAC),可以接收本机网卡上所有的数据帧...
  • C语言套接字编程TCP连接

    热门讨论 2011-12-18 15:13:40
    C语言套接字编程TCP连接,实现功能.先启动服务器端,然后,启动客户端。服务器IP地址要改。
  • Socket套接字编程

    千次阅读 2018-09-18 17:57:48
    前天面试了环信公司,在...(1)基于UDP协议的socket套接字编程 UDP协议是非链接的协议,它不与对方建立连接,而是直接把要发送的数据发送给对方。所以UDP协议适用于一次传输数据量很少,对可靠性要求不高的应用场...
  • 套接字编程简介

    2016-03-16 21:45:27
    整理自《Unix网络编程卷一》第三章 套接字通信主要目的是实现数据的交互,但是为了实现数据的交互我们通常还有许多工作要做。...+ 数据的发送、接收函数套接字地址结构 大多数套接字编程主要用于网络通信(Uni
  • 5.套接字编程:socket编程 基于UDP协议的socket客户端与服务端通信编程 服务端: 创建套接字; 在内核创建struct socket结构体,使进程和网卡之间建立联系 -1. int socket(int domain, int type, int ...
  • 原始套接字编程和之前的 UDP 编程差不多,无非就是创建一个套接字后,通过这个套接字接收数据或者发送数据。区别在于,原始套接字可以自行组装数据包(伪装本地 IP,本地 MAC),可以接收本机网卡上所有的数据帧...
  • 1.与流式套接字编程相比不同的函数: sendto():数据报套接字描述符;要发送的字节序列的缓冲区;发送缓冲区的长度;套接字调用的方式;一个指向sockaddr结构的指针,保存服务器的端口号和IP地址;指定地址结构的...
  • 断开已连接的套接字主要分为三个步骤:首先关闭套接字的发送和接收数据功能,接下来关闭套接字,最后释放Winsock动态库资源。 1 关闭套接字发送和接收数据功能 通过shutdown()函数关闭套接字的发送和接收数据的...
  • tcp套接字编程模型

    2016-05-15 11:30:48
    tcp套接字编程模型。
  • 网络编程>UDP套接字编程

    千次阅读 2019-04-21 14:45:00
    UDP套接字编程 典型的UDP客户/服务器程序的函数调用如下: 1、缓冲区 发送缓冲区用虚线表示,任何UDP套接字都有发送缓冲区,不过该缓冲区仅能表示写到该套接字的UDP数据报的上限。如果应用进程写一...
  • 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应用程序开发语言,他的许多特性可以...
  • (1)熟悉Linux环境下数据报套接字编程的基本模型、函数使用细节等,掌握Linux环境下数据报套接字编程的具体过程。 (2)熟悉Windows环境下数据报套接字编程的基本模型、函数使用细节等,掌握Windows环境下数据报...
  • 套接字编程常用函数

    千次阅读 2016-03-16 21:46:23
    不同协议的套接字编程(TCP套接字、UDP套接字、原始套接字等)的模型有所差异,但一般会使用到一下常用函数: #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, ...
  • 近期学习了并发套接字编程,通过查找资料发现,并发套接字编程可以通过三种方式来实现: 1.多线程 2.多进程 3.多路复用 在上一篇博客中,我介绍了简单的socket模型,并且利用多进程实现了并发套接字编程。但是,我...
  • Linux网络编程:原始套接字编程及实例分析 转载 2016年07月29日 11:25:11 标签: socket / 网络编程 / 编程 / linux / 888 编辑 删除 Linux网络编程:原始套接字编程及实例分析 一、原始套接字能干什么? ...
  • Python 网络编程(套接字编程),python socket实现服务端与客户端的简单通信,socket编程模拟ssh的部分功能以及自动应答
  • 基本TCP套接字编程

    千次阅读 2019-03-11 12:14:20
    基本TCP套接字编程 connect函数 #include&lt;sys/socket.h&gt; int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen) 返回:若成功则为0,若出错则为-1 客户端调用...
  • linux套接字编程

    千次阅读 2013-08-18 14:41:41
    套接字编程的各级要素: `主机接口:网络地址ip `协议:特定的协议(TCP & UDP) `端口:client或server的进程终点 套接字 简单的说,套接字就是两个应用程序之间通信管道的终点,这个管道可以唯一的标志一...
  • MFC—windows套接字编程

    2019-07-30 09:07:34
    Windows 套接字编程 一、常见概念 1、Windows Sockets 规范 Windows Sockets 规范是 Windows 平台下定义的可以兼容二进制数据传输的网络编程接口,是基于伯克利加利福尼亚大学的 BSD UNIX Sockets 的实现;规范...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 21,356
精华内容 8,542
关键字:

套接字编程