关于套接字(sockets)入门的一篇好文章

套接字编程(sockets 译文)

作者:Kameswari Chebrolu (印度理工学院坎普尔分校电子工程系) 卡门斯瓦力 车布罗

 

 

背景

多路解编

●转变“主机到主机的数据包传输服务”到“程序到程序的数据交流通道”

 

字节次序

●两类“字节次序”

    ----网络字节次序:一个数从高位字节开始存在存储器中的最低位地址;

    ----主机字节次序:一个数从低位字节开始存在存储器中的最低位地址;

    ----网络堆栈(TCP/IP)需要按网络字节次序存储

 

●转变

    ----htons() --> host(主机) to network(网络) Short(Short类型)

    ----htonl() --> host(主机) to network(网络) Long(Long类型)

    ----ntohs() --> network(网络) to host(主机) Short(Short类型)

    ----ntohl() --> network(网络) to host(主机) Long(Long类型)

 

 

套接字是什么?

●套接字:应用程序和数据运输层的接口

    -应用程序可以通过套接字发送或接受另一个应用程序(本地或远程)的数据

 

●用Unix术语说,一个套接字是一个文件描述符——一个与公开文件相关联的整数

 

●套接字类型:Internet Sockets(互联网套接字),unix sockets(Unix套接字),X.25 sockets等

    -Internet sockets 由IP地址(4字节)和端口数(2字节)描述

 

 

套接字说明书

封装

 

 

网络套接字的类型

●Sream Sockets(SOCK_STREAM)

    -面向连接的

    -依赖TCP提供可靠的两地连接通信

●Datagram Sockets(SOCK_DGRAM)  Datagram数据包

    -依赖于UDP

    -连接是不可靠的

 

 

socket()函数 -- 获取文件描述符

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

    -domaim(域)应该被设为PF_INET(PF -> protocol family 协议族)

    -type 可以是 SOCK_STREAM or SOCK_DGRAM

    -设置protocol为0使套接字基于type选择恰当的协议

    -socket() 返回一个套接字描述符,可在之后的系统调用中使用;如果错误函数返回-1

 

    int sockfd;

    sockfd = socket(PF_INET,SOCK_STREAM,0);

 

 

套接字的结构体

●struct sockaddr:为许多类型的套接字获取其地址信息

struct sockaddr {

    unsigned short sa_family;    //地址族 AF_XXX

    unsigned short sa_data[14];  //14字节的协议地址

}

 

●struct sockaddr_in:一种使得引用套接字地址更加容易的平行结构体

Struct sockaddr_in {

    short int            sin_family;    //设置 AF_INET

    unsigned short int    sin_port;      //端口号

    struct in_addr       sin_addr;      //网络地址

    unsigned char    sin_zero[8];   //全设为0

}

 

●sin_port和sin_addr必须是网络字节次序

 

 

处理IP地址

● struct in_addr {

    unsigned long s_addr;//32位long类型或4字节

};

 

int inet_aton(const char* cp, struct in_addr* inp);

    struct sockaddr_in my_addr;

    my_addr.sin_family = AF_INET;

    my_addr.sin_port = htons(MYPORT);

    inet_aton(10.0.0.5, &(my_addr.sin_addr));//功能是将一个字符串IP地址转换为一 个32位的网络序列IP地址

    memset(&(my_addr.sin_zero), ’\0’, 8);

    -inet-aton() 返回不是0则操作成功;是0则操作失败

 

●转变二进制IP为字符串: inet_ntoa()

    Printf(“%s”, inet_ntoa(my_addr.sin_addr));

 

 

 

bind()- 我在那个端口?   (bind 捆绑)

●常在本地计算机上用端口关联套接字

    -内核使用端口号将收到的数据包与程序相匹配

 

●int bind(int sockfd, struct sockaddr* my_addr, int addrlen)

    -sockfd 是一个套接字描述符,由socket()函数返回

    -my_addr 是sockaddr结构体的指针,包含本机IP地址和端口的信息

    -addrlen 要设置为sockaddr结构体的长度  sizeof(struct sockaddr)

    -my_addr.sin_port = 0;   //任意选择一个没用的端口

    -my_addr.sin_addr.s_addr = INADDR_ANY; //使用IP地址

 

例子

int sockfd;

struct sockaddr_in my_addr;

sockfd = socket(PF_INET, SOCK_STREAM, 0);

my_addr.sin_family = AF_INET;    //主机字节次序

my_addr.sin_port = htos(MYPORT); //short类型,网络字节次序

my_addr.sin_addr.s_addr = inet_addr(“172.28.44.57”);

memset(&(my_addr,sin_zero),’\0’,8);//将结构体剩余部分置为0

bind(sockdf, (struct sockaddr*)&my_addr, sizeof(struct sockaddr));

/********** 代码需要错误检查。不要忘了********/

 

connect()函数——你好!

●连接到一个远程主机

 

●int connect(int sockfd, struct sockaddr* serv_addr, int addrlen)

    -sockfd 是一个套接字描述符,由socket()函数返回

    -serv_addr 是sockaddr结构体的指针,包含目标IP地址和端口的信息

    -addrlen 要设置为sockaddr结构体的长度  sizeof(struct sockaddr)

    -返回-1则操作错误

 

●不需要bind()去捆绑,内核会帮助选择端口

 

 

例子

#define DEST_IP “172.28.44.57”

#define DEST_PORT 5000

main(){

    int sockfd;

    struct sockaddr_in dest_addr;  //将持有目的地址

    sockfd = socket(PF_INET, SOCK_STREAM, 0);

    dest_addr.sin_family = AF_INET;//主机字节次序

    dest_addr.sin_port = htons(DEST_PORT);//网络字节次序

    dest_addr.sin_addr.s_addr = met_addr(DEST_IP);

    memset(&(dest_addr.sin_zero), ’\0’, 8);//将结构体剩余部分置为0

    struct connect(sockfd, (struct sockaddr*)&dest_addr,sizeof(struct sockaddr));

}

/**********不要忘记错误检测***********/

 

listen() —— 请给我回电话

●等待传入的连接

●int listen(int sockfd, int backlog);

    -sockfd 是一个套接字描述符,由socket()函数返回

    -backlog 是在传入队列中,连接被允许的数量

    -listen() 返回-1则操作错误

    -在你能够listen()前需要调用bind()

●socket()

●bind()

●listen()

●accept()

 

accept() —— 感谢你的呼叫!

●accept() 获取端口上你正在listen()的挂起的连接

●int accept(int sockfd, void* addr, int* addrlen);

    -sockfd是监听套接字描述符

    -收到的连接信息被存在addr,addr是指向本地sockaddr_in结构体的指针

    -addrlen 要设置为sockaddr_in结构体的长度  sizeof(struct sockaddr_in)

   -accept()返回一个新的套接字文件描述符用于这个接受到的连接,或者返回-1表示操作错误

 

 

例子

#include <string.h>

#include <sys/types.h>

#include <sys.socket.h>

#include <netinet/in.h>

#define MYPORT 3490  //端口使用者将连接此

#define BACKLOG 10   //挂起的连接队列将持有

main() {

int sockfd, new_fd;    //监听于sockfd,新连接于new_fd

struct sockaddr_in my_addr; //我的地址信息

struct sockaddr_in their_addr;//连接者的地址信息

int sin_size;

sockfd = socket(PF_INET, SOCK_STREAM, 0);

my_addr.sin_family = AF_INET;//主机字节次序

my_addr.sin_port = htons(MYPORT);//short类型,网络字节次序

my_addr.sin_addr.s_addr = INADDR_ANY;//用我的IP自动填充

memset(&(my_addr.sin_zero), ‘\0’, 8);//将结构体剩余部分置为0

//不要忘记错误检查

bind(sockfd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr));

listen(sockfd, BACKLOG);

sin_size = sizeof(struct sockaddr_in);

new_fd = accept(sockfd, (struct sockaddr*)&their_addr, &sin_size);

 

 

send()和recv() —— 让我们讲话!

●两个函数是通过流套接字或者建立连接的数据报套接字来沟通的

 

●int send(int sockfd, const void* msg, int len, int flags);

    -sockfd是一个你想传送数据过去的套接字描述符(由socket()返回或从accept()获取)

    -msg是指向你想传输的数据的指针

    -len是你想传输的数据的字节大小

    -将flags暂时设置为0

    -sent()返回实际上传输了的字节数(可能比你指定的要小,即小于len);返回-1则操作错误

 

●int recv(int sockfd, void* buf, int len, int flags)

    -sockfd是一个你想读出数据的套接字描述符

    -buf将信息读入一个缓冲区

    -len是缓冲区的最大长度

    -将flags暂时设置为0

    -recv()返回实际上读入缓冲区的字节数;返回-1则操作错误

    -如果recv()返回0,那么远程端关闭与你的连接

 

sendto()和recvfrom() —— DGRAM style

●int sendto(int sockfd, const void* msg, int len, int flags, const struct sockaddr* to, int tolen);

    -to是一个指向sockaddr结构体的指针,包含目标IP和端口

    -tolen是sockaddr的长度 sizeof(struct sockaddr)

 

●int recvfrom(int sockfd, void* buf, int len, int flags, struct sockaddr* from, int* fromlen);

    -from 是一个本地sockaddr结构体的指针,里面会装信息发出端的IP地址和端口

    -fromlen是存储在from中的地址的长度

 

Close() —— 再见!

●int close(int sockfd);

    -关闭套接字描述符对应的连接并且释放套接字描述符

    -防止多余的发送(send())和接受(recv())

 

面向连接协议

 

无连接协议

 

 

其他常规

●int getpeername(int sockfd, struct sockaddr* addr, int* addrlen);

    -会告诉你同样在连接结束环节的流套接字,并将信息存在addr中

 

●int gethostname(char* hostname, size_t size);

    -会获得正在运行你的程序的计算机名称,并存在hostname中

 

●struct hostent* gethostbyname(const char* name);

struct hostent {

    char* h_name;    //主机的名称

    char **h_aliases; //主机的替代名称

    int  h_addrtype;  //常为AF_NET

    int  h_length;    //地址的字节长度

    char **h_addr_list;//主机的网络地址数组

}

#define h_addr h_addr_list[0]

 

●代码示例

struct hostent *h;

h = gethostbyname(“www.iitk.ac.in”);

printf(“Host name:%s\n”,h->h-name);

printf(“IP Address:%s\n”,inet_ntoa(*((struct in_addr*)h->h_addr)));

 

高级主题

●Blocking  阻塞 封锁

●Select    选择

●Handling partial sends 解决局部发送

●Signal handlers 信号处理器

●Threading 线程

 

总结

●套接字用标准Unix文件描述符帮助应用程序和其他应用程序建立交流

●两类互联网套接字:SOCK_STREAM 和 SOCK_DGRAM

●许多常规帮助简化程序的交流

 

参考

●书目:

    -Unix Network Programming, volumes 1-2 by W.Richard Stevens.

    -TCP/IP llustrated, volumes 1-3 by W.Richard Stevens and Gray R.Wright

 

●网络资源:

    -Beej’s Guide to Network Programming

        ●www.ecst.csuchico.edu/~beej/guide/net/

 

 

猜你喜欢

转载自blog.csdn.net/MrBang_/article/details/83032204
今日推荐