1、socket函数:
作用:创建套接字
#include <sys/socket.h>
int socket(int domain, int type, int protocol); // 成功则返回非负描述符,错误则返回-1
- domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE和AF_KET等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
- type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_SEQPACKET等。
- protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议。
setsockopt() 函数:用于任意类型、任意状态套接字的设置选项值。函数原型为:
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
- sockfd:标识一个套接口的描述字。
- level:选项定义的层次;支持SOL_SOCKET(基本套接口)、IPPROTO_TCP(TCP套接口)、IPPROTO_IP(IPv4套接口)和IPPROTO_IPV6(IPv6套接口)。
- optname:需设置的选项,详细的所有选项参考这里。
SOL_SOCKET |
|
IPPROTO_TCP |
|
IPPROTO_IP |
|
IPPROTO_IPV6 |
|
- optval:指针,指向存放选项待设置的新值的缓冲区。
- optlen:optval缓冲区长度。
2、bind函数:
作用:套接字和本地协议地址和端口进行绑定
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // 成功返回0,错误则返回-1
- sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
- addrlen:对应的是地址的长度。
- addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同。
(1)ipv4对应的是:
struct sockaddr_in {
sa_family_t sin_family; /* 地址簇,为:AF_INET */
in_port_t sin_port; /* 端口号,通常为htons(port) */
struct in_addr sin_addr; /* ip地址,这个值通常可以指定为htonl(INADDR_ANY),INADDR_ANY通常为0,表示由内核去选择IP地址 */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
(2)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 */
};
(3)Unix域对应的是:
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
(4)地址查询
getaddrinfo函数允许将一个主机名和一个服务名映射到一个地址,利用这个函数返回的sockaddr,可以方便地创建socket、bind和listen。
#include <netdb.h>
int getaddrinfo(const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result ); // 成功则返回0,出错返回非0错误码
typedef struct addrinfo {
int ai_flags; //AI_PASSIVE,AI_CANONNAME,AI_NUMERICHOST
int ai_family; //AF_INET,AF_INET6
int ai_socktype; //SOCK_STREAM,SOCK_DGRAM
int ai_protocol; //IPPROTO_IP, IPPROTO_IPV4, IPPROTO_IPV6 etc.
size_t ai_addrlen; //must be zero or a null pointer
char* ai_canonname; //must be zero or a null pointer
struct sockaddr* ai_addr; //must be zero or a null pointer
struct addrinfo* ai_next; //must be zero or a null pointer
}
- hostname:一个主机名或者地址串(IPv4的点分十进制串或者IPv6的16进制串)
- service:服务名可以是十进制的端口号,也可以是已定义的服务名称,如ftp、http等
- hints:可以是一个空指针,也可以是一个指向某个addrinfo结构体的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。举例来说:如果指定的服务既支持TCP也支持UDP,那么调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM使得返回的仅仅是适用于数据报套接口的信息。
- result:本函数通过result指针参数返回一个指向addrinfo结构体链表的指针。
3、网络字节序和主机字节序
- 主机字节序:
就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:
a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
- 网络字节序:
4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。
所以:在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。
#include <netinet/in.h>
unit16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue); //均返回网络字节序的值
unit16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue); //均返回主机字节序的值
4、listen函数:
作用:把一个未连接套接字转为一个被动套接字,只在服务器端调用
#include <sys/socket.h>
int listen(int sockfd, int backlog);
- 第一个参数即为要监听的socket描述字
- 第二个参数为相应socket可以排队的最大连接个数。
5、accept函数:
作用:从一个已完成队列取出一个已完成连接,只在服务器端调用。
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 第一个参数为服务器的socket描述字
- 第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址
- 第三个参数为协议地址的长度。
- 返回值:如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。 出错则返回-1。
6、connect函数:
作用:建立和服务器的连接,只在客户端调用。
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 第一个参数即为客户端的socket描述字
- 第二参数为服务器的socket地址
- 第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。
7、读写socket的函数
7.1 read和write
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
- read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。
- write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数。失败时返回-1,并设置errno变量。 在网络程序中,当我们向套接字文件描述符写时有俩种可能。1)write的返回值大于0,表示写了部分或者是全部的数据。2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。
7.2 readv和writev
read()和write()系统调用每次在文件和进程的地址空间之间传送一块连续的数据。但是,应用有时也需要将分散在内存多处地方的数据连续写到文件中,或者反之。在这种情况下,如果要从文件中读一片连续的数据至进程的不同区域,使用read()则要么一次将它们读至一个较大的缓冲区中,然后将它们分成若干部分复制到不同的区域,要么调用read()若干次分批将它们读至不同区域。同样,如果想将程序中不同区域的数据块连续地写至文件,也必须进行类似的处理。UNIX提供了另外两个函数—readv()和writev(),它们只需一次系统调用就可以实现在文件和进程的多个缓冲区之间传送数据,免除了多次系统调用或复制数据的开销。readv()称为散布读,即将文件中若干连续的数据块读入内存分散的缓冲区中。writev()称为聚集写,即收集内存中分散的若干缓冲区中的数据写至文件的连续区域中。
#include <sys/uio.h>
ssize_t readv(int fildes, const struct iovec *iov, int iovcnt);
ssize_t writev(int fildes, const struct iovec *iov, int iovcnt);
- 参数fildes是文件描述字。
- iov是一个结构数组,它的每个元素指明存储器中的一个缓冲区。结构类型iovec有下述成员,分别给出缓冲区的起始地址和字节数:
struct iovec {
void *iov_base /* 数据区的起始地址 */
size_t iov_len /* 数据区的大小 */
}
- 参数iovcnt指出数组iov的元素个数,元素个数至多不超过IOV_MAX。linux中定义IOV_MAX的值为1024。
7.3 send和recv
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
recv和send函数提供了和read和write差不多的功能.不过它们提供了第四个参数来控制读写操作。第四个参数可以是0或者是以下的组合:
| MSG_DONTROUTE | 不查找表 |
| MSG_OOB | 接受或者发送带外数据 |
| MSG_PEEK | 查看数据,并不从系统缓冲区移走数据 |
| MSG_WAITALL | 等待所有数据 |
- MSG_DONTROUTE:是 send函数使用的标志.这个标志告诉IP.目的主机在本地网络上面,没有必要查找表.这个标志一般用网络诊断和路由程序里面.
- MSG_OOB:表示可以接收和发送带外的数据.
- MSG_PEEK:是recv函数的使用标志, 表示只是从系统缓冲区中读取内容,而不清除系统缓冲区的内容.这样下次读的时候,仍然是一样的内容.一般在有多个进程读写数据时可以使用这个标志.
- MSG_WAITALL :是recv函数的使用标志,表示等到所有的信息到达时才返回.使用这个标志的时候recv回一直阻塞,直到指定的条件满足,或者是发生了错误. 1)当读到了指定的字节时,函数正常返回.返回值等于len 2)当读到了文件的结尾时,函数正常返回.返回值小于len 3)当操作发生错误时,返回-1,且设置错误为相应的错误号(errno)。
7.4 sendto和recvfrom
tcp是连接协议,udp是无连接协议。在没有connect的情况下能发送数据才算udp。在无连接的情况下,write和read无法传输数据,所以发送数据需要用sendto,接收数据需要用revefrom。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
参数介绍:
socketfd:socket描述符;
buf: UDP数据报缓存区(包含待发送数据);
len: UDP数据报的长度;
flags:调用方式标志位(一般设置为0);
to: 指向接收数据的主机地址信息的结构体(sockaddr_in需类型转换);
tolen:to所指结构体的长度;
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
参数介绍:
socketfd:标识一个已连接套接口的描述字。
buf:接收数据缓冲区。
len:缓冲区长度。
flags:调用操作方式。
from:(可选)指针,指向装有源地址的缓冲区。
fromlen:(可选)指针,指向from缓冲区长度值。
7.5 sendmsg和recvmsg
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
这两个函数把大部分参数封装到一个 msghdr 结构体中:
struct msghdr {
void *msg_name; // protocol address
socklen_t msg_namelen; // size of protocol address
struct iovec *msg_iov; // scatter/gather array
int msg_iovlen; // elements in msg_iov
void *msg_control; // ancillary data (cmsghdr struct)
socklen_t msg_controllen; // length of ancillary data
int msg_flags; // flags returned by recvmsg()
};
- msg_name 和 msg_namelen 这两个成员用于套接字未连接的场合(如未连接 UDP 套接字)。它们类似 recvfrom 和 sendto 的第五个和第六个参数:
- msg_iov 和 msg_iovlen 这两个成员指定输入或输出缓冲区数组(即iovec结构数组),类似 readv 或 writev 的第二个和第三个参数。
- msg_control 和 msg_controllen 这两个成员指定可选的辅助数据的位置和大小。msg_controllen 对于 recvmsg 是一个值-结果参数。
8、close函数:
作用:关闭套接字
#include <unistd.h>
int close(int fd);
9、TCP服务器和客户端示例程序
9.1 服务器端
int main(int argc, char **argv) {
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = socket(AF_INET, SOCK_STREAM,0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
listen(listenfd, LISTENQ);
for( ; ; ) {
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);
if((childpid = fork()) == 0) {
close(listenfd);
str_echo(connfd);
exit(0);
}
close(connfd);
}
return 0;
}
void str_echo(int sockfd) {
ssize_t n;
char buf[MAXSIZE];
again:
while ((n = read(sockfd, buf, MAXLINE)) > 0) {
writen(sockfd, buf, n);
}
if (n < 0 && errno == EINTR) {
goto again;
}
else if (n < 0) {
err_sys("str_echo:read error");
}
}
9.2 客户端
int main(int argc, char **argv) {
int sockfd,
struct sockaddr_in servaddr;
if (argc !=2 ) {
err_quit("usage:tcpcli<IPaddress>");
}
sockid = socket(AF_INET, SOCK_STREAM.0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
str_cli(stdin, sockfd);
exit(0);
}
void str_cli(FILE *fp, int sockfd) {
char sendline[MAXLINE], recvline[MAXLINE];
while (fgets(sendline, MAXLINE, fp) != NULL) {
writen(sockfd, sendline, strlen(sendline));
if (readline(sockfd, recvline, MAXLINE) == 0) {
err_quit("str_cli:server terminated prematurely");
}
fputs(recvline, stdout);
}
}
10、UDP服务器和客户端示例程序
10.1 服务器端程序
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr, cliaddr;
sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(4099);
Bind(sockfd, (SA *)&servaddr, sizeof(servaddr));
server_echo(sockfd, (SA *)&cliaddr, sizeof(cliaddr));
exit(0);
}
void server_echo(int sockfd, SA *cliaddr, socklen_t clilen)
{
int n;
socklen_t len;
char mesg[MAXLINE];
while (1) {
len = clilen;
n = Recvfrom(sockfd, mesg, MAXLINE, 0, cliaddr, &len);
Sendto(sockfd, mesg, n, 0, cliaddr, len);
}
}
10.2 客户端程序
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2) {
err_quit("usage: a.out <IPaddress>");
}
sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(4099);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
client_echo(stdin, sockfd, (SA *)&servaddr, sizeof(servaddr));
exit(0);
}
void client_echo(FILE *fp, int sockfd, SA *servaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
while (fgets(sendline, MAXLINE, fp) != NULL) {
Sendto(sockfd, sendline, strlen(sendline), 0, servaddr, servlen);
n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
recvline[n] = 0;
fputs(recvline, stdout);
}
}