Socket 编程

来源:互联网 发布:淘宝确认收货 花呗 编辑:程序博客网 时间:2024/06/15 20:58

1 网络进程之间的通信

1.1 本地进程通信

  • 消息传递(管道,消息队列)
  • 同步(互斥,读写锁)
  • 共享内存

1.2 网络进程通信

网络之间如何通信,首先要解决的是如何标识一个唯一的进程,本地进程可以用PID来标识,但这种方式没有办法应用在网络通信中。tcp/ip协议已经帮我们解决了这个问题,协议中的 ip层可以识别唯一主机,而传输层中的“协议+端口”可以识别唯一的应用程序(进程)。也就是说利用(ip,协议,端口)可以标识进程。

2 什么是socket

socket 起源于unix系统,unix系统有一个基本的哲学概念——一切皆文件,也就是说一切东西可以按照文件模式“打开—读、写—关闭”进行操作。即可以把socket视为一种特殊的文件进行操作。

3 socket 基本操作

3.1基本流程

这里写图片描述

3.2 socket()函数

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

socket()函数用于创建一个socket描述字,用来唯一标识一个socket。把socket视为一种特殊的文件的话,那么socket()相当于创建文件描述字,用作后续对文件进行读写操作的参数。

socket的三个参数

  • domain 协议域,常用的有 AF_INET(和ipv4对应) ,AF_INET6(和ipv6对应)
  • type socket类型。常用的有,SOCK_STREAM和SOCK_DGRAM
  • protocol 协议 常用的有IPPROTO_TCP、IPPTOTO_UDP分别对应 tcp 和udp

3.3 bind()函数

int bind(int socket_fd , const struct sockaddr *addr , socklen_t addrlen )

三个参数

  • socket_fd socket描述字,bind()用于将socket()函数创建的描述字绑定一个名字
  • addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同
    ipv4对应的是:
struct sockaddr_in {    sa_family_t    sin_family; /* address family: AF_INET */    in_port_t      sin_port;   /* port in network byte order */    struct in_addr sin_addr;   /* internet address */};
/* Internet address. */struct in_addr {    uint32_t       s_addr;     /* address in network byte order */};
 ipv6对应的是: 
struct sockaddr_in6 {     sa_family_t     sin6_family;   /* AF_INET6 */     in_port_t       sin6_port;     /* port number */     uint32_t        sin6_flowinfo; /* IPv6 flow information */     struct in6_addr sin6_addr;     /* IPv6 address */     uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ };
struct in6_addr {     unsigned char   s6_addr[16];   /* IPv6 address */ };
  • addrlen:对应的是地址的长度。

3.4 listen() 、connect()函数

作为一个服务器,在调用socket()、bind()之后就会调用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服务器的连接。

3.5 accept()函数

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

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • 第一个参数为服务器的socket描述字,
  • 第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址
  • 第三个参数为协议地址的长度。

如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

3.6 read() 、write()函数

现在可以对文件(socket)进行I/O操作了,常用的函数有:

  • read() 、 write()
  • recv()、send() 用于tcp
  • recvfrom()、sendto() 用于udp

3.7 close()函数

关闭套接字

int close(int fd)

close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。

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

4 简单实例

server.cpp 只是简单的demo

#include <cstdio>#include <iostream>#include <cstring>#include <cstdlib>#include <iostream>#include <errno.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#define BACKLOG 10 using namespace std ;int main(int argc , char **argv){    int serv_fd , client_fd ;    struct sockaddr_in servaddr , clientaddr ;    serv_fd = socket(AF_INET , SOCK_STREAM , IPPROTO_TCP) ;    memset(&servaddr , 0 , sizeof(servaddr)) ;    servaddr.sin_family = AF_INET ;    servaddr.sin_port = htons(10000) ;    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1") ;    bind(serv_fd , (struct sockaddr *) &servaddr , sizeof(servaddr) ) ;    listen(serv_fd , BACKLOG) ;    while(1)    {    client_fd = accept(serv_fd , (struct sockaddr *) NULL , NULL) ;    //while(1)    //{        char recv[40] , send_word[40] ;        read(client_fd , recv , sizeof(recv) - 1 ) ;        cout << recv << endl ;        cin >> send_word ;        write(client_fd , send_word , sizeof(send_word)) ;    //}    close(client_fd) ;    }    close(serv_fd) ;    return 0 ;}

client.cpp demo

#include <iostream>#include <stdlib.h>#include <errno.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <unistd.h>#include <arpa/inet.h>#define BACKLOG 10using namespace std ;int main(int argc , char **argv){    int client_fd ;    struct sockaddr_in servaddr , clientaddr ;    if(argc != 2)    {        cout << "input error, usage: ./client ip" <<endl ;        exit(0) ;    }    client_fd = socket(AF_INET , SOCK_STREAM , IPPROTO_TCP) ;     in_addr_t server_ip = inet_addr(argv[1]) ;    memset(&clientaddr , 0 , sizeof(clientaddr) ) ;    clientaddr.sin_family = AF_INET ;    clientaddr.sin_port = htons(10001) ;    clientaddr.sin_addr.s_addr = inet_addr("127.0.0.1") ;    bind(client_fd , (struct sockaddr *)&clientaddr , sizeof(sockaddr) ) ;    memset(&servaddr , 0 , sizeof(servaddr)) ;    servaddr.sin_family = AF_INET ;    servaddr.sin_port = htons(10000) ;    servaddr.sin_addr.s_addr = server_ip ;    connect(client_fd , (struct sockaddr *)&servaddr , sizeof(servaddr)) ;   // while(1)    //{        char buffer[40] , send_word[40] ;        cin >> send_word ;        write(client_fd , send_word , sizeof(send_word)) ;        read(client_fd , buffer , sizeof(buffer) - 1) ;        cout << buffer << endl ;    //}    close(client_fd) ;    return 0 ;}