Socket编程

 

Socket编程

1、socket中的数据结构和数据处理

 

a socket descriptor:int
 
struct sockaddr 含有地址信息    
struct sockaddr {
    unsigned short sa_family; // address family, AF_xxx
    char sa_data[14]; // 14 bytes of protocol address
};

struct sockaddr_in {
    short int sin_family; // Address family
    unsigned short int sin_port; // Port number
    struct in_addr sin_addr; // Internet address
    unsigned char sin_zero[8]; // Same size as struct sockaddr
};

// Internet address (a structure for historical reasons)
struct in_addr {
    unsigned long s_addr; // that’s a 32-bit long, or 4 bytes
};

这 两个结构的作用是一样的,通常多数都是用sockaddr_in,他比较直观。但是socket API中都是用的sockaddr,所以使用这个数据结构需要做强制类型转换。第四个域sin_zero使用menset()将其设置为0就好了。还有就 是,sin_port 和 sin_addr必须是网络字节顺序,使用htons()。

the sin_family field is only used by the kernel to determine what type of address the structure contains, so it must be in Host Byte Order.

 

因为sin_family只是给kernel用来决定结构所包含的地址的类型,所以它必须是主机字节顺序的。

 

1.1、用于处理字节顺序的函数:

 

• htons() – "Host to Network Short"
• htonl() – "Host to Network Long"
• ntohs() – "Network to Host Short"
• ntohl() – "Network to Host Long"

 

1.2、IP地址及其处理

 

首先定义一个struct sockadd_in:

 

struct sockaddr_in ina;

假定我的ip是10.12.110.57,那么可以用inet_addr()将ip地址填入该结构:

 

ina.sin_addr.s_addr = inet_addr("10.12.110.57");

还有,inet_addr()处理后的就已经是网络字节顺序了,所以不需要处理字节顺序。

 

inet_addr()返回-1表示出错,注意-1正好是二进制数对应的255.255.255.255!要作错误检查。

 

相对于inet_addr(),还有个更好用的提供类似功能的接口inet_aton()(“ation”表示“asiic to network):

 

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
 

看一个简单的例子:

 

struct sockaddr_in my_addr;
my_addr.sin_family = AF_INET; // host byte order
my_addr.sin_port = htons(MYPORT); // short, network byte order
inet_aton("10.12.110.57", &(my_addr.sin_addr));
memset(&(my_addr.sin_zero), ’\0’, 8); // zero the rest of the struct

不同一般的socket api,inet_aton()返回非0表示成功,返回0表示失败,而且不是所有平台都实现这个接口。

 

类似地,inet_aton()是将字符串的IP地址转换成二进制表示,inet_ntoa()二进制表示转换成字符串的IP(数字以点隔开)。

 

printf("%s", inet_ntoa(ina.sin_addr));

注意;inet_ntoa()需要一个struct in_addr作为参数,并且返回一个指针,可以使用strcpy()拷贝IP地址。


2、socket API

 

2.1、socket()–Get the File Descriptor!

 

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

@domain:AF_INET

 

@type:SOCK_STREAM|SOCK_DGRAM

 

@protocol:设置为0,让socket()基于@type选择协议,当然还有更好的选择协议的方法,man getprotobyname()。

 

返回一个文件描述符,-1表示错误,同时设置全局变量errno。

 

2.2、bind()–What port am I on?

 

一旦有了一个socket,就可以将socket和本地机器上的端口关联。端口是kernel用来将接收到的包和某个进程的socket文件描述符匹配。

 

#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

@sockfd:上面得到的socket,也就是要关联端口的socket

 

@my_addr:port、IP

 

@addrlen:sizeof(struct sockaddr)

 

看个例子:

 

#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MYPORT 3490
main()
{
int sockfd;
struct sockaddr_in my_addr;

sockfd = socket(AF_INET, SOCK_STREAM, 0); // do some error checking!

my_addr.sin_family = AF_INET; // host byte order
my_addr.sin_port = htons(MYPORT); // short, network byte order
my_addr.sin_addr.s_addr = inet_addr("10.12.110.57");
memset(&(my_addr.sin_zero), ’\0’, 8); // zero the rest of the struct

// don’t forget your error checking for bind():
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
.
.
.

 

IP和port可以通过下面的动作bind()自动获取:

 

my_addr.sin_port = 0; // choose an unused port at random
my_addr.sin_addr.s_addr = INADDR_ANY; // use my IP address

 

bind()同样也是返回-1表示错误,同时设置errno。

 

注意:port的范围,1024以后是系统分配了的,所以可以在1024--65535之间取值。


2.3、connect()

 

#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

@sockfd: socket() returned

 

@serv_addr: 目标主机,即你要连的服务器

 

@addrlen:sizeof(struct sockaddr)

 

看个例子:

 

#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define DEST_IP "10.12.110.57"
#define DEST_PORT 23
main()
{
int sockfd;
struct sockaddr_in dest_addr; // will hold the destination addr

sockfd = socket(AF_INET, SOCK_STREAM, 0); // do some error checking!

dest_addr.sin_family = AF_INET; // host byte order
dest_addr.sin_port = htons(DEST_PORT); // short, network byte order
dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);
memset(&(dest_addr.sin_zero), ’\0’, 8); // zero the rest of the struct
// don’t forget to error check the connect()!

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

同样的,返回-1表示错误,同时设置errno。


2.4、listen()监听连接的到来

 

int listen(int sockfd, int backlog);

@socket:得自socket()

 

@backlog:请求队列的长度

 

同样地,-1表示错误,同时设置errno。


2.5、accept()接受连接请求

 

在accept之前,所有的连接请求都在请求队列中等待被accepted。accept()将返回一个新的socket文件描述符用于处理每个连接,这样将新产生一些socket文件描述符,同时原来的那个socket文件描述符仍然监听端口。

 

#include <sys/socket.h>
int accept(int sockfd, void *addr, int *addrlen);

@sockfs:监听端口的那个socket文件描述符

 

@addr:一个指向本地的struct sockaddr_in的指针,这个用来存放远程连接的信息

 

@addrlen:sizeof(struct sockaddr_in)

 

看个例子:

 

#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MYPORT 3490 // the port users will be connecting to
#define BACKLOG 10 // how many pending connections queue will hold
main()
{
int sockfd, new_fd; // listen on sock_fd, new connection on new_fd
struct sockaddr_in my_addr; // my address information
struct sockaddr_in their_addr; // connector’s address information
int sin_size;

sockfd = socket(AF_INET, SOCK_STREAM, 0); // do some error checking!

my_addr.sin_family = AF_INET; // host byte order
my_addr.sin_port = htons(MYPORT); // short, network byte order
my_addr.sin_addr.s_addr = INADDR_ANY; // auto-fill with my IP
memset(&(my_addr.sin_zero), ’\0’, 8); // zero the rest of the struct
// don’t forget your error checking for these calls:

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

注意:通信过程中数据的接收和发送都是通过后来accept()返回的socket文件描述符来传送的。


4.6、send() recv()数据的收发

 

这两个接口是用于stream socket或者有连接的datagram socket,如果要无连接的datagram通信,需使用sendto() 和 recvfrom()。

 

原型:

 

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

@sockfd:用于通信的socket文件描述符

 

@msg:消息,通信的内容

 

@flags:0,更多信息查看man send

 

返回发送的字节数,有可能小于len,-1表示错误,并且errno。

 

int recv(int sockfd, void *buf, int len, unsigned int flags);

@sockfd:用于接口的socket文件描述符

 

@buf:用于存放接口的消息

 

@flags:0,更多信息查看man recv

 

返回接收到的字节数,-1表示出错,并且errno,如果返回的是0表示远程连接已经关闭。


4.7、sendto() recvfrom() DGRAM格式的通信

 

相对于有连接的通信,无连接的通信需要提供更多的关于通信双方的信息,所以sendto()和recvfrom()都多2个参数。

 

原型:

 

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

@sockfd:

 

@msg:

 

@flags:

 

这三个和send()是一样的

 

@to:一个指针,指向struct sockaddr,目标的IP和PORT

 

@tolen:sizeof(struct sockadr)

 

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

@sockfd:

 

@buf:

 

@flags:

 

这三个和recv()是一样的

 

@from:

 

@fromlen:

 

4.8、close() shutdown()

 

close(sockfd);
int shutdown(int sockfd, int how);

@sockfd:想要断开的那个socket

 

@how:断开的方式

 

• 0 – Further receives are disallowed

 

• 1 – Further sends are disallowed

 

• 2 – Further sends and receives are disallowed (like close())

 

返回0表示成功,-1表示出错



注意:

shutdown不会关闭文件描述符,它只是改变文件描述符的可用性。要释放文件描述符,需调用close()。


4.9、getpeername()

 

#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

 

4.10 gethostname()

 

#include <unistd.h>
int gethostname(char *hostname, size_t size);