1、所用到的头文件:
sys/types.h:数据类型定义
sys/socket.h:提供socket函数及数据结构
netinet/in.h:定义数据结构sockaddr_in
arpa/inet.h:提供IP地址转换函数
netdb.h:提供设置及获取域名的函数(DNS相关)
sys/ioctl.h:提供对I/O控制的函数
sys/poll.h:提供socket等待测试机制的函数
其他在网络程序中常见的头文件
unistd.h:提供通用的文件、目录、程序及进程操作的函数
errno.h:提供错误号errno的定义,用于错误处理
fcntl.h:提供对文件控制的函数(控制 socket 是否 blocking)
time.h:提供有关时间的函数(select 需要用到)
crypt.h:提供使用DES加密算法的加密函数
pwd.h:提供对/etc/passwd文件访问的函数
shadow.h:提供对/etc/shadow文件访问的函数
pthread.h:提供多线程操作的函数
signal.h:提供对信号操作的函数
sys/wait.h、sys/ipc.h、sys/shm.h:提供进程等待、进程间通讯(IPC)及共享内存的函数
1、网络地址结构体:
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
};
2、网络字节序转换函数:
htons() – "Host to Network Short"
htonl() – "Host to Network Long"
ntohs() – "Network to Host Short"
ntohl() – "Network to Host Long"
3、IP地址转换函数:
inet_addr()
ina.sin_addr.s_addr = inet_addr("10.12.110.57");
int inet_aton(const char *cp, struct in_addr *inp);
注意:返回0为失败!返回非0为成功!
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
inet_ntoa()
printf("%s", inet_ntoa(ina.sin_addr));
返回值保存在一个静态变量中,每次调用这个函数,均会刷新返回值。
4、socket()–Get the File Descriptor!
int socket(int domain, int type, int protocol);
domain: AF_INET
type: SOCK_STREAM, SOCK_DGRAM
protocol: 0
返回:socket 文件描述符,或 -1,错误代码在errno。
5、bind()–What port am I on?
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
参数:
addrlen:sizeof(struct sockaddr)
返回:错误-1,错误代码在errno
自动获取IP地址:
my_addr.sin_addr.s_addr = INADDR_ANY
自动获取端口号:
my_addr.sin_port = 0
6、connect()–Hey, you!
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));
返回:错误-1,错误代码在errno
当用来连接的socket没有进行bind时,会自动将此socket bind到一个任意端口上。
7、listen()–Will somebody please call me?
int listen(int sockfd, int backlog);
参数:
sockfd:如果不进行bind,则操作系统会任意将此socket bind到一个任意端口!
backlog:接入等待队列长度,默认为20
返回:错误-1,错误代码在errno
8、accept()–"Thank you for calling port 3490."
int accept(int sockfd, void *addr, int *addrlen);
参数:
sockfd:用来listen的socket
addr:用来接收连接方的地址信息
addrlen:设定为 sizeof(struct sockaddr),期望接收的地址信息长度,如果接收少了的话,accept函数会改变此值。
返回:一个新的socket 描述符!错误返回-1,错误代码在errno。
9、send() –Talk to me, baby!
int send(int sockfd, const void *msg, int len, int flags);
参数:
sockfd:socket描述符
msg:要发送的信息
len:发送信息的长度
flags:0
返回:实际发送的长度,错误返回-1,错误代码在errno。
注意:实际发送的长度有可能小于期望发送的长度!需要循环发送。
10、recv()–Talk to me, baby!
int recv(int sockfd, void *buf, int len, unsigned int flags);
参数:
buf:接收缓冲区
len:接收缓冲区长度
返回:实际接收的长度,错误返回-1,错误代码在errno。返回0,说明对方关闭了链接。
11、sendto() –Talk to me, DGRAM-style
int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);
参数:
to:对方的地址和端口。
tolen:sizeof(struct sockaddr)
返回:实际发送的长度(有可能小于期望发送的长度!),错误返回-1,错误代码在errno。
12、recvfrom()–Talk to me, DGRAM-style
int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);
参数:
from:发送方的地址信息。
fromlen:期望接收的发送方的地址信息长度(sizeof(struct sockaddr)),如果实际接收比这个值小,则recvfrom函数会更改这个参数。
注意:如果将SOCK_DGRAM类型的socket connect之后,可以使用send(),recv()函数收发数据,函数将自动添加相应的地址信息!但是仍然使用的是UDP协议!
13、close() and shutdown()–Get outta my face!
close(sockfd); 双向关闭一个socket,不能再进行收发操作。
int shutdown(int sockfd, int how);
参数:
how:0不能继续读,1不能继续写,2不能继续读写
返回:0成功,错误返回-1,错误代码在errno
注意:
如果使用shutdown在connect之后的SOCK_DGRAM类型的socket上,则会禁止使用send()和recv()进行数据传输。
shutdown没有真正关闭socket文件标示符,只是改变了可用性。如果需要真正关闭,需要调用close()
14、getpeername()–Who are you?
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
得到一个连接后的SOCK_STREAM类型socket对方的地址信息。
15、gethostname()–Who am I?
int gethostname(char *hostname, size_t size);
返回当前主机的主机名。
16、DNS
struct hostent *gethostbyname(const char *name);
数据结构:
struct hostent
{
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
};
#define h_addr h_addr_list[0]
返回:错误返回NULL,错误代码在h_errno
17、Blocking
sockfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(sockfd, F_SETFL, O_NONBLOCK);
这样处理之后的socket在recv() 或者recvfrom()之后不会block,如果没有数据,会返回-1,errorno会被设定为“EWOULDBLOCK”
注意:这种方式会造成忙等待。
18、select()–Synchronous I/O Multiplexing
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
同时监视多组文件描述符,如果有文件描述符可以被(读、写、出现异常),select返回,并且将对应文件描述符组修改为出现时间的文件描述符。通过检测每个文件描述符组,就可以知道出现事件的文件描述符。
参数:
numfds:应该被设定为最大的文件描述符+1(越是后建立的socket,描述符越大)
readfds:用来读取的文件描述符组,当不关心时,设置为NULL
writefds:用来写入的文件描述符组,当不关心时,设置为NULL
exceptfds:出现异常的文件描述符组,当不关心时,设置为NULL
timeout:超时时间,当超过这个时间后,select将直接返回。当设定为NULL时,永不超时。
操作fd_set的宏定义:
FD_ZERO(fd_set *set) – clears a file descriptor set
FD_SET(int fd, fd_set *set) – adds fd to the set
FD_CLR(int fd, fd_set *set) – removes fd from the set
FD_ISSET(int fd, fd_set *set) – tests to see if fd is in the set
拷贝两个fd_set:a = b
时间结构体:
struct timeval
{
int tv_sec; // seconds
int tv_usec; // microseconds
};
19、循环发送
int sendall(int s, char *buf, int *len)
{
int total = 0; // how many bytes we’ve sent
int bytesleft = *len; // how many we have left to send
int n;
while(total < *len) {
n = send(s, buf+total, bytesleft, 0);
if (n == -1) { break; }
total += n;
bytesleft -= n;
}
*len = total; // return number actually sent here
return n==-1?-1:0; // return -1 on failure, 0 on success
}