C++网络编程

文章目录[x]
  1. 1:socket介绍
  2. 2:网络中进程之间通信
  3. 3:socket基本操作
  4. 3.1:socket函数
  5. 3.2:binf()函数
  6. 3.3:网络字节序和主机字节序
  7. 3.4:listen()、connect()函数
  8. 3.5:accept()函数
  9. 3.6:close函数
  10. 3.7:socket中的TCP的三次握手建立和四次挥手
  11. 4:完整代码

socket网络编程

socket介绍

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

网络中进程之间通信

网络层重的IP地址可以作为唯一的标识,而传输层的协议+端口可以作为主机中唯一的应用程序,利用三元组(ip地址,协议,端口)就可以标识网络的进程。

使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX BSD的套接字(socket)

socket基本操作

socket函数

int socket(int domain,int type,int protocol);

socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。

  • domain:协议族,常用协议族AF_INETAF_INET6AF_LOCAL(AF_UNNIX)、AF_ROUTE

  • type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET

  • protocol:指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议

协议族:

  • AF_INET:(TCP/IP-IPv4)

  • AF_INET6(TCP/IP – IPv6)

  • AF_UNIX(本机通信)

socket类型:

  • SOCK_STREAM:流格式套接字也叫“面向连接的套接字”。 是一种可靠的、双向的通信数据流,数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送。

  • SOCK_DGRAM:数据报格式套接字(Datagram Sockets)也叫“无连接的套接字“。

    计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。

    因为数据报套接字所做的校验工作少,所以在传输效率方面比流格式套接字要高。

    binf()函数

    bind()函数把一个地址族中的特定地址赋给socket。

    int bind (int sockfd,const struct sockaddr *addr,socklen_t addrlen);
    • sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。

    • addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是:

    • struct sockaddr_in {
          sa_family_t    sin_family; 
          in_port_t      sin_port;   
          struct in_addr sin_addr;   
      };
      
      sockaddr_in seraddr;
        seraddr.sin_addr.s_addr = INADDR_ANY;//监听任意地址
        seraddr.sin_port = htons(80);//绑定端口
        seraddr.sin_family = AF_INET;//绑定协议族

      ipv6对应的是:

      struct sockaddr_in6 { 
          sa_family_t     sin6_family;    
          in_port_t       sin6_port;      
          uint32_t        sin6_flowinfo;  
          struct in6_addr sin6_addr;      
          uint32_t        sin6_scope_id;  
      };
    • addrlen:对应的是地址的长度。

    网络字节序和主机字节序

    主机字节序

    大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:

    1. Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

    2. Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

    网络字节序

    4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。

    listen()、connect()函数

    如果是服务器端,则调用listen()来监听这个socket,如果是客户端这时调用的则为connect()来发起请求。

    int listen(int sockfd,int backlog);
    int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

    listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

    connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。

    accept()函数

    TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就想TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

    注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

    close函数

    在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。

    int close(int fd);

    注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

    socket中的TCP的三次握手建立和四次挥手

    三次握手

    1. 客户端向服务器发送一个SYN J

    2. 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1

    3. 客户端再向服务器发一个确认ACK K+1

当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。

四次挥手

  1. 假设Client端发起中断连接请求,也就是发送FIN报文。

  2. Server端接到FIN报文后,意思是说"我Client端没有数据要发给你了",但是如果你还有数据没有发送完成,则不必急着关闭Socket,可以继续发送数据。所以你先发送ACK,"告诉Client端,你的请求我收到了,但是我还没准备好,请继续你等我的消息"。

  3. 这个时候Client端就进入FIN_WAIT状态,继续等待Server端的FIN报文。当Server端确定数据已发送完成,则向Client端发送FIN报文,"告诉Client端,好了,我这边数据发完了,准备好关闭连接了"。

  4. Client端收到FIN报文后,"就知道可以关闭连接了,但是他还是不相信网络,怕Server端不知道要关闭,所以发送ACK后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。“,Server端收到ACK后,"就知道可以断开连接了"。Client端等待了2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,我Client端也可以关闭连接了。Ok,TCP连接就这样关闭了!

完整代码

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/sendfile.h>
#include<fcntl.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
const int port = 8888;
int main(int argc,char *argv[])
{
    if(argc<0)
    {
        printf("need two canshu\n");
        return 1;
    }
    int sock;
    int connfd;
    struct sockaddr_in sever_address;
    bzero(&sever_address,sizeof(sever_address));
    sever_address.sin_family = PF_INET;
    sever_address.sin_addr.s_addr = htons(INADDR_ANY);
    sever_address.sin_port = htons(8888);
 
    sock = socket(AF_INET,SOCK_STREAM,0);
 
    assert(sock>=0);
 
    int ret = bind(sock, (struct sockaddr*)&sever_address,sizeof(sever_address));
    assert(ret != -1);
 
    ret = listen(sock,1);
    assert(ret != -1);
    while(1)
    {
        struct sockaddr_in client;
        socklen_t client_addrlength = sizeof(client);
        connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);
        if(connfd<0)
        {
            printf("errno\n");
        }
        else{
                char request[1024];
                recv(connfd,request,1024,0);
                request[strlen(request)+1]='\0';
                printf("%s\n",request);
                printf("successeful!\n");
                char buf[520]="HTTP/1.1 200 ok\r\nconnection: close\r\n\r\n";//HTTP响应
                int s = send(connfd,buf,strlen(buf),0);//发送响应
                //printf("send=%d\n",s);
                int fd = open("hello.html",O_RDONLY);//消息体
                sendfile(connfd,fd,NULL,2500);//零拷贝发送消息体
                close(fd);
                close(connfd);
        }
    }
    return 0;
}
点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像