socket网络编程

来源:互联网 发布:深圳网络推广qebang 编辑:程序博客网 时间:2024/06/08 13:49

1. 两个简单的例子

1.1. TCP 协议通信

客户端程序

#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>#include <signal.h>#include <stdio.h>#include <string.h>#include <netinet/in.h>  #include <unistd.h>#define SERVER_PORT 12345int main(int argc, char *argv[]){    int iSocketFd;    int iError = 0;    struct sockaddr_in tServerAddr;    int iSockLen;    unsigned char ucSendBuf[1024];    int iSendBufLen = 0;     if(argc != 2){        printf("exe <ipaddr>\n");        return -1;    }    iSocketFd = socket(AF_INET, SOCK_STREAM, 0);         /* 建立一个 socket */    if(-1 == iSocketFd){        printf("build socket error\n");        return -1;    }    tServerAddr.sin_family      = AF_INET;  /* 属于IPv4 */    tServerAddr.sin_port        = htons(SERVER_PORT); /* host to net, short */    inet_aton(argv[1], (struct in_addr *)&tServerAddr.sin_addr);    iError = connect(iSocketFd, (const struct sockaddr *)&tServerAddr,                   sizeof(struct sockaddr));    /* 对指定的目标主机进行连接 */    if(iError){        printf("failed to connect\n");        return -1;    }    while(1){        scanf("%s", ucSendBuf);        iError =  send(iSocketFd, ucSendBuf, sizeof(ucSendBuf), 0);  /* 发送数据 */        if(iError <= 0){            printf("Send buffer error\n");            return -1;        }    }    return 0;}

服务器端

#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>#include <signal.h>#include <stdio.h>#include <netinet/in.h>  #include <unistd.h>#define LISTEN_PORT 12345#define MAX_CONNECT 5int main(int argc, char *argv[]){    int iSocketServerFd;    int iSocketclientFd;    int iError = 0;    struct sockaddr_in tServerAddr;    struct sockaddr_in tClientAddr;    int iSockLen;    int iClientIdentify = 0;    unsigned char ucRecvBuf[1024];    int iRecvBufLen = 0; /* * SOCK_STREAM : Provides sequenced, reliable, two-way, connection-based byte streams *               连续的,可靠的,双向的,字节流 */    iSocketServerFd = socket(AF_INET, SOCK_STREAM, 0);  /* 建立一个 socket */    if(-1 == iSocketServerFd){        printf("build socket error\n");        return -1;    }    tServerAddr.sin_family      = AF_INET;  /* 属于IPv4 */    tServerAddr.sin_port        = htons(LISTEN_PORT); /* host to net, short */    tServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);  /* 监控所有的ip INADDR_ANY = 0.0.0.0*/    /* 绑定地址到socket */    iError = bind(iSocketServerFd, (const struct sockaddr *)&tServerAddr,                    sizeof(struct sockaddr));  /* 绑定 socket 与 IP */    iError |= listen(iSocketServerFd, MAX_CONNECT); /* 监听,最多MAX_CONNECT个 */    if(iError){        printf("failed to listen\n");        return -1;    }    /* 防止由于子进程退出产生僵死进程 */    signal(SIGCHLD,SIG_IGN);    while(1){        iSockLen      = sizeof(struct sockaddr);        /* 接受连接,此函数会阻塞等待,直到有连接 */        iSocketclientFd = accept(iSocketServerFd, (struct sockaddr *)&tClientAddr, &iSockLen);        if(-1 != iSocketclientFd){            printf("connect from %s, connect %d\n", (char *)inet_ntoa(tClientAddr.sin_addr.s_addr),                 iClientIdentify++);            if(!fork()){   /* 创建子线程,!fork() 表明是主进程 */                while(1){                    /* 接收数据,会阻塞 */                    iRecvBufLen = recv(iSocketclientFd, ucRecvBuf, sizeof(ucRecvBuf), 0);                    if(iRecvBufLen > 0){                        ucRecvBuf[iRecvBufLen] = '\0';                        printf("Receive buf %s, form %s\n", ucRecvBuf, (char *)inet_ntoa(tClientAddr.sin_addr.s_addr));                    }else{                        close(iSocketclientFd);                        return -1;                    }                }            }        }    }    close(iSocketServerFd);    return 0;}

测试
首先启动服务器端,然后根据客户端的使用提示进行连接,在客户端输入字符之后回车能够看到在服务器端接收到来自客户端的数据

  • client 端执行
./client 192.168.2.102
- 立马可以在服务器端看到
connect from 192.168.2.102, connect 0
- 客户端输入
hellow world 回车
- 服务器端可以看到
Receive buf hellow, form 192.168.2.102Receive buf world, form 192.168.2.102

1.2 UDP

客户端

#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>#include <signal.h>#include <stdio.h>#include <string.h>#include <netinet/in.h>  #include <unistd.h>#define SERVER_PORT 12345int main(int argc, char *argv[]){    int iSocketFd;    int iError = 0;    struct sockaddr_in tServerAddr;    int iSockLen;    unsigned char ucSendBuf[1024];    int iSendBufLen = 0;     if(argc != 2){        printf("exe <ipaddr>\n");        return -1;    }    iSocketFd = socket(AF_INET, SOCK_DGRAM, 0);    if(-1 == iSocketFd){        printf("build socket error\n");        return -1;    }    tServerAddr.sin_family      = AF_INET;  /* 属于IPv4 */    tServerAddr.sin_port        = htons(SERVER_PORT); /* host to net, short */    if(0 == inet_aton(argv[1], (struct in_addr *)&tServerAddr.sin_addr)){        printf("Wrong addr\n");        return -1;    }    while(1){        scanf("%s", ucSendBuf);        iSendBufLen =  sendto(iSocketFd, ucSendBuf, sizeof(ucSendBuf), 0,                      (const struct sockaddr *)&tServerAddr, sizeof(struct sockaddr));        if(iSendBufLen <= 0){            printf("Send buffer error\n");            return -1;        }    }    return 0;}

服务器端

#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>#include <signal.h>#include <stdio.h>#include <netinet/in.h>  #include <unistd.h>#define LISTEN_PORT 12345int main(int argc, char *argv[]){    int iSocketServerFd;    int iSocketclientFd;    int iError = 0;    struct sockaddr_in tServerAddr;    struct sockaddr_in tClientAddr;    int iSockLen;    int iClientIdentify = 0;    unsigned char ucRecvBuf[1024];    int iRecvBufLen = 0; /* * SOCK_STREAM : Provides sequenced, reliable, two-way, connection-based byte streams *               连续的,可靠的,双向的,字节流 */    iSocketServerFd = socket(AF_INET, SOCK_DGRAM, 0);    if(-1 == iSocketServerFd){        printf("build socket error\n");        return -1;    }    tServerAddr.sin_family      = AF_INET;  /* 属于IPv4 */    tServerAddr.sin_port        = htons(LISTEN_PORT); /* host to net, short */    tServerAddr.sin_addr.s_addr = INADDR_ANY;  /* 监控所有的ip INADDR_ANY = 0.0.0.0*/    /* 绑定地址到socket */    iError = bind(iSocketServerFd, (const struct sockaddr *)&tServerAddr,                    sizeof(struct sockaddr));    if(-1 == iError){        printf("Bind error\n");        return -1;    }    while(1){        iSockLen = sizeof(struct sockaddr);        iRecvBufLen = recvfrom(iSocketServerFd, ucRecvBuf, sizeof(ucRecvBuf), 0,                               (struct sockaddr *)&tClientAddr, &iSockLen);        if(iRecvBufLen > 0){            printf("%s : %s\n", (char *)inet_ntoa(tClientAddr.sin_addr), ucRecvBuf);        }    }    close(iSocketServerFd);    return 0;}

测试:方法同上面一样

两者的区别:

  • UDP 不需要进行连接,直接朝目标发送数据即可,UDP 是用户数据报,无连接的
  • TCP 需要先进行连接,之后才能够发送数据,TCP 是传输控制协议,需要连接,比 UDP 更加可靠,因为 TCP 有重传机制,而 UDP 协议只是将数据传输过去,并不关心对方是否接收到了数据

1.3 步骤

  • 服务器端

    • 获得一个套接字描述符
    • 设置地址信息
    • 绑定套接字描述符到地址信息结构体
    • 监听套接字
    • 等待接受连接,获得连接方的套接字描述符
    • 连接成功之后,接收或者发送字符
  • 客户端

    • 获得一个套接字描述符
    • 设置地址信息
    • 连接
    • 连接成功之后选择接收或者发送数据

2. 套接字

套接字是通信端点的抽象,可以类比看作是文件,文件可以存储内容,从套接字里面我们也可以读取到来自其他地方发送到该端点的数据,如同使用文件描述符(句柄)访问文件一样,套接字描述符用来进行套接字的访问

int socket(int domain, int type, int protocol);    /* 获得一个套接字描述符 */domain    AF_INET    IPv4 因特网域    AF_INET6   IPv6 因特网域    AF_UNIX    UNIX 域      AF_LOCAL    AF_UNIX 别名    AF_UPSPEC  未指定,可用于所有的域type    SOCK_DGRAM        固定长度的、无连接的、不可靠的报文传输    SOCK_RAW          IP 协议的数据报接口    SOCK_SEQPACKET    固定长度的、有序的、可靠的、面向连接的报文传递    SOCK_STREAM       有序的、可靠的、双向的、面向连接的字节流protocol    IPPROTO_IP      IPv4 网际协议    IPPROTO_IPV6    IPv6 网际协议    IPPROTO_ICMP    因特网控制报文协议    IPPROTO_RAW     原始 IP 数据报协议    IPPROTO_TCP     传输控制协议    IPPROTO_UDP     用户数据报协议

一般来说 protocol 保持 0,意思是保持默认值,对于 SOCK_STREAM 来说,其默认的 protocol 是 TCP 协议;对于 SOCK_DGRAM 来说,其默认的协议是 UDP 协议

  • UDP:协议包里面包涵数据的次序,数据的目的地址。因为这是无连接的,不可靠的协议,所以当数据包被发送出去的时候,并不知道哪一个包会先行到达,并且有可能会丢失,每一包数据都会有目的地址
  • TCP:事先建立一个连接,数据如果有丢失的话会有重传机制,包里面不含有特定的数据地址,因为已经建立了点对点的虚拟连接链路
close 函数可以关闭打开的 socket,还有另外一种关闭方式int shutdown(int sockfd, int how);sockfd    套接字描述符how    SHUT_RD    关闭读取端   在上面例子的服务器端里面就是读取端,要在服务器端执行    SHUT_WR    关闭写入端   在上面例子的客户端里面就是写入端,要在客户端执行    SHUT_RDWR  关闭读写端   双面都可以执行,执行过后都可以到达效果shutdown 的用途就是在套接字描述符被多个地方引用的时候使用,如果使用 close 函数,要等到所有的引用都关闭之后才会释放这个套接字,而 shutdown 可以立即使某个套接字处于不活动状态

3. 地址格式

地址格式在不同的系统中有不同的定义方式,在 linux 中

struct socketaddr{    sa_family_t sa_family;    /* 地址类 */    char        sa_data[14];  /* 可变长度的地址 */}struct in_addr{    in_addr_t    s_addr;      /* IPv4 的地址 */}struct socketaddr_in{    sa_family_t    sin_family;    /* 地址类,16字节 */    in_port_t      sin_port;      /* 端口号,16字节 */    struct in_addr sin_addr;      /* IPv4 的地址,32字节 */    unsigned char  sin_zero[8];   /* 填充字段,用不到 */}

上面两者的长度都是 16 字节

由于各种系统不同的字节序,有的是大端,有的是小端,但是网络字节序是确定的,所以系统提供了统一的函数进行地址到字符串或者字符串到地址的转换

  • 网络地址与主机地址转换
         host to net longuint32_t htonl(uint32 hostint32);     返回以网络字节序表示的 32 位整数uint16_t htons(uint16 hostint16);     返回以网络字节序表示的 16 位整数uint32_t ntohl(uint32_t netint32);    返回以主机字节序表示的 32 位整数uint16_t ntohs(uint16_t netint16);    返回以主机字节序表示的 16 位整数
  • IP 地址 . 分十进制与字符串之间的转换
in_addr_t inet_addr(const char *cp);    字符串到点分十进制char *inet_ntoa(struct in_addr in);     点分十进制到字符串int inet_aton(const char *cp, struct in_addr *inp);    字符串到点分十进制上面的函数仅支持 IPv4 
  • 得到主机的信息
/* node 主机名,可以是节点名或者是点分格式的主机地址 * service 服务名,如 http,nfs,tcp * hints 过滤器,不过滤的字段设置为 0 * res   返回的地址信息,是一个链表结构成员,需要用 freeaddrinfo 释放 */int getaddrinfo(const char *node, const char *service,                       const struct addrinfo *hints,                       struct addrinfo **res);void freeaddrinfo(struct addrinfo *res);struct addrinfo {       int              ai_flags;       int              ai_family;       int              ai_socktype;       int              ai_protocol;       size_t           ai_addrlen;       struct sockaddr *ai_addr;       char            *ai_canonname;       struct addrinfo *ai_next;};
  • 将套接字与地址关联
sockaddr 一般是用 sockaddr_in 结构体代替,然后再强制转换过去int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);使用限制- 在进程正在运行的计算机上,指定的地址必须有效,不能指定一个其它机器的地址- 地址必须和创建套接字时候的地址族所支持的格式匹配- 地址端口号必须大于 1024,否则需要权限- 一般只能将一个套接字绑定到一个给定的地址上面,尽管有些协议允许多重绑定,例如 TCP 只能绑定一个主机到同一个端口,即使 IP 不同,主机相同也不行
  • 建立连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

指定的地址是我们想与之通信的服务器地址。如果 sockfd 没有绑定到一个地址,connect 会给调用者绑定一个默认的地址

客户端

tGetSockAddr.sin_family      = AF_INET; /* 属于IPv4 */tGetSockAddr.sin_port        = htons(SERVER_PORT); /* host to net, short */memset(tGetSockAddr.sin_zero, 0, 8);inet_aton("192.168.2.100", (struct in_addr *)&tGetSockAddr.sin_addr);iError = bind(iSocketFd, (const struct sockaddr *)&tGetSockAddr, sizeof(struct sockaddr));if(iError){    printf("Bind error\n");}printf("tGetSockAddr.sin_family = %d\n", (int)tGetSockAddr.sin_family);printf("tGetSockAddr.sin_port = %d\n", ntohs(tGetSockAddr.sin_port));printf("tGetSockAddr.sin_addr = %s\n", (char *)inet_ntoa(tGetSockAddr.sin_addr));while(1){    scanf("%s", ucSendBuf);    iError =  send(iSocketFd, ucSendBuf, sizeof(ucSendBuf), 0);    if(iError <= 0){        printf("Send buffer error\n");    }    iRecvBufLen = recv(iSocketFd, ucRecvBuf, sizeof(ucRecvBuf), 0);    if(iRecvBufLen > 0){        ucRecvBuf[iRecvBufLen] = '\0';        printf("%s\n", ucRecvBuf);    }else{        printf("Receive error\n");    }}

服务器端

if(!fork()){   /* 创建子线程,!fork() 表明是主进程 */    while(1){        iRecvBufLen = recv(iSocketclientFd, ucRecvBuf, sizeof(ucRecvBuf), 0);        if(iRecvBufLen > 0){            ucRecvBuf[iRecvBufLen] = '\0';            printf("Receive buf %s, form %s\n", ucRecvBuf, (char *)inet_ntoa(tClientAddr.sin_addr.s_addr));            send(iSocketclientFd, "I received your buff", 256, 0);        }else{            close(iSocketclientFd);            return -1;        }    }}
  • 客户端,不同的主机,如果没有初始化绑定 sockfd
struct socketaddr length = 16struct socketaddr_in length = 16tGetSockAddr.sin_family = 0tGetSockAddr.sin_port = 0tGetSockAddr.sin_addr = 3.0.0.0sdfhdsjfI received your buff
  • 客户端,相同主机,初始化绑定 sockfd
struct socketaddr length = 16struct socketaddr_in length = 16Bind error    //绑定出错tGetSockAddr.sin_family = 2tGetSockAddr.sin_port = 12345tGetSockAddr.sin_addr = 192.168.3.56

不同的主机绑定成功,同一个主机绑定就不成功

4. 接收与发送数据

  • send 要事先通过 connect 进行连接,面向连接
  • recv 接受数据,数据中不含有发送者的地址信息
  • sendto 不需要实现建立连接,参数包含目标地址,无连接
  • recvfrom 接收信息中含有发送端的地址信息
0 0
原创粉丝点击