linux C 编程 之 socket 网络编程

网络编程:

1.基本函数

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

int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
sockfd:是由socket 调用返回的文件描述符.
addrlen:是sockaddr 结构的长度.
my_addr:是一个指向sockaddr 的指针.

int listen(int sockfd,int backlog);
sockfd:是bind 后的文件描述符.
backlog:设置请求排队的最大长度.

int accept(int sockfd, struct sockaddr *addr,int *addrlen);
sockfd:是listen 后的文件描述符.
addr,addrlen 是用来给客户端的程序填写的,服务器端只要传递指针就可以了. bind,li
sten 和accept 是服务器端用的函数,accept 调用时,服务器端的程序会一直阻塞到有一个
客户程序发出了连接. accept 成功时返回最后的服务器端的文件描述符,这个时候服务
器端可以向该描述符写信息了. 失败时返回-1.

int connect(int sockfd, struct sockaddr * serv_addr,int addrlen);
sockfd:socket 返回的文件描述符.
serv_addr:储存了服务器端的连接信息.其中sin_add 是服务端的地址
addrlen:serv_addr 的长度
connect 函数是客户端用来同服务端连接的.成功时返回0,sockfd 是同服务端通讯的文件
描述符 失败时返回-1.

2.字节转换函数.

unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unisgned short int hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);

在这四个转换函数中,h 代表host, n 代表 network.s 代表short l 代表long 第一个函
数的意义是将本机器上的long 数据转化为网络上的long. 其他几个函数的意义也差不多

3.IP 和域名的转换

在网络上标志一台机器可以用IP 或者是用域名.那么我们怎么去进行转换呢?
struct hostent *gethostbyname(const char *hostname);
struct hostent *gethostbyaddr(const char *addr,int len,int type);

在<netdb.h>;中有struct hostent 的定义
struct hostent{
char *h_name; /* 主机的正式名称 */
char *h_aliases; /* 主机的别名 */
int h_addrtype; /* 主机的地址类型 AF_INET*/
int h_length; /* 主机的地址长度 对于IP4 是4 字节32 位*/
char **h_addr_list; /* 主机的IP 地址列表 */
}
#define h_addr h_addr_list[0] /* 主机的第一个IP 地址*/
gethostbyname 可以将机器名(如 linux.yessun.com)转换为一个结构指针.在这个结构里
面储存了域名的信息
gethostbyaddr 可以将一个32 位的IP 地址(C0A80001)转换为结构指针.
这两个函数失败时返回NULL 且设置h_errno 错误变量,调用h_strerror()可以得到详细的
出错信息.

4.字符串的IP 和32 位的IP 转换.

在网络上面我们用的IP 都是数字加点(192.168.0.1)构成的, 而在struct in_addr 结构中
用的是32 位的IP, 我们上面那个32 位IP(C0A80001)是的192.168.0.1 为了转换我们可以
使用下面两个函数
int inet_aton(const char *cp,struct in_addr *inp);
char *inet_ntoa(struct in_addr in);

函数里面 a 代表 ascii n 代表network.第一个函数表示将a.b.c.d 的IP 转换为32 位的I
P,存储在 inp 指针里面.第二个是将32 位IP 转换为a.b.c.d 的格式.

5.服务信息函数

在网络程序里面我们有时候需要知道端口.IP 和服务信息.这个时候我们可以使用以下几
个函数
int  getsockname(int sockfd,struct sockaddr *localaddr,int *addrlen);
int  getpeername(int sockfd,struct sockaddr *peeraddr, int *addrlen);
struct  servent *getservbyname(const char *servname,const char *protoname);
struct  servent *getservbyport(int port,const char *protoname);

struct servent
{
char *s_name; /* 正式服务名 */
char **s_aliases; /* 别名列表 */
int s_port; /* 端口号 */
char *s_proto; /* 使用的协议 */
}

6.读写函数

ssize_t write(int fd,const void *buf,size_t nbytes);
读函数read
ssize_t read(int fd,void *buf,size_t nbyte) ;
read 函数是负责从fd 中读取内容.当读
成功时,read 返回实际所读的字节数,如果返回的值是0 表示已经读到文件的结束了,小于
0 表示出现了错误.如果错误为EINTR 说明读是由中断引起的, 如果是ECONNREST 表示网
络连接出了问题.

7.基于UDP读写.

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

sockfd,buf,len 的意义和read,write 一样,分别表示套接字描述符,发送或接收的缓冲区及大小.
recvfrom 负责从sockfd 接收数据,如果from 不是NULL,那么在from 里面存储了信息
来源的情况,如果对信息的来源不感兴趣,可以将from 和fromlen 设置为NULL.sendto 负责向to 发送信息.此时在to 里面存储了收信息方的详细资料.


7.recv 和send 函数提供了和read 和write 差不多的功能.不过它们提供 了第四个参数来控制
读写操作.
int recv(int sockfd,void *buf,int len,int flags)
int send(int sockfd,void *buf,int len,int flags)

前面的三个参数和read,write 一样,第四个参数可以是0 或者是以下的组合
| MSG_DONTROUTE | 不查找路由表 |
| MSG_OOB | 接受或者发送带外数据 |
| MSG_PEEK | 查看数据,并不从系统缓冲区移走数据 |
| MSG_WAITALL | 等待所有数据 |

recvmsg 和sendmsg 可以实现前面所有的读写函数的功能.
int recvmsg(int sockfd,struct msghdr *msg,int flags)
int sendmsg(int sockfd,struct msghdr *msg,int flags)
struct msghdr
{
void *msg_name;
int msg_namelen;
struct iovec *msg_iov;
int msg_iovlen;
void *msg_control;
int msg_controllen;
int msg_flags;
}
struct iovec
{
void *iov_base; /* 缓冲区开始的地址 */
size_t iov_len; /* 缓冲区的长度 */
}
msg_name 和 msg_namelen 当套接字是非面向连接时(UDP),它们存储接收和发送方的地址
信息.msg_name 实际上是一个指向struct sockaddr 的指针,msg_name 是结构的长度.当套
接字是面向连接时,这两个值应设为NULL. msg_iov 和msg_iovlen 指出接受和发送的缓冲
区内容.msg_iov 是一个结构指针,msg_iovlen 指出这个结构数组的大小. msg_control 和
msg_controllen 这两个变量是用来接收和发送控制数据时的 msg_flags 指定接受和发送
的操作选项.和recv,send 的选项一样.

8.关闭socket

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

TCP 连接是双向的(是可读写的),当我们使用close 时,会把读写通道都关闭,有时侯我们希
望只关闭一个方向,这个时候我们可以使用shutdown.针对不同的howto,系统回采取不同
的关闭方式.
howto=0 这个时候系统会关闭读通道.但是可以继续往接字描述符写.
howto=1 关闭写通道,和上面相反,着时候就只可以读了.
howto=2 关闭读写通道,和close 一样 在多进程程序里面,如果有几个子进程共享一个套接
字时,如果我们使用shutdown, 那么所有的子进程都不能够操作了,这个时候我们只能够
使用close 来关闭子进程的套接字描述符.

9.getsockopt 和setsockopt

int getsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optlen);
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t*optlen);

level 指定控制套接字的层次.可以取三种值:
 1)SOL_SOCKET:通用套接字选项. 
 2)IPPROTO_IP:IP 选项. 
 3)IPPROTO_TCP:TCP 选项.
optname 指定控制的方式(选项的名称),我们下面详细解释
optval 获得或者是设置套接字选项.根据选项名称的数据类型进行转换

10.ioctl 可以控制所有的文件描述符的情况,这里介绍一下控制套接字的选项.

int ioctl(int fd,int req,...);

二,并发服务器:多路复用I/O;

int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *except fds,struct timeval *timeout);
void FD_SET(int fd,fd_set *fdset);
//将fd 加入到fdset
void FD_CLR(int fd,fd_set *fdset);
//将fd 从fdset 里面清除
void FD_ZERO(fd_set *fdset);
//从fdset 中清除所有的文件描述符

int FD_ISSET(int fd,fd_set *fdset);//判断fd 是否在fdset 集合中

poll:

struct pollfd {
int fd; /* 文件描述符*/
short events; /* 等待的需要测试事件 */
short revents; /* 实际发生了的事件,也就是返回结果 */
};
  int poll(struct pollfd *fds, nfds_t nfds, int timeout);

这样就可以监听fds里面文件描述符了,当满足特定条件就返回,并将结果保存在revents中。

epoll:

epoll可以理解为event poll,不同于忙轮询和无差别轮询(select),epoll会把哪个流发生了怎样的I/O事件通知我们。此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))
    在讨论epoll的实现细节之前,先把epoll的相关操作列出:
epoll_create 创建一个epoll对象,一般epollfd = epoll_create()
epoll_ctl (epoll_add/epoll_del的合体),往epoll对象中增加/删除某一个流的某一个事件
比如
epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//注册缓冲区非空事件,即有数据流入(可读事件)
epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//注册缓冲区非满事件,即流可以被写入(可写事件)
epoll_wait(epollfd,...)等待直到注册的事件发生
(注:当对一个非阻塞流的读写发生缓冲区满或缓冲区空,write/read会返回-1,并设置errno=EAGAIN。而epoll只关心缓冲区非满和缓冲区非空事件)。
一个epoll模式的代码大概的样子是:
while true {
active_stream[] = epoll_wait(epollfd)
for i in active_stream[] {
read or write till
}
}
 限于篇幅,我只说这么多,以揭示原理性的东西,至于epoll的使用细节,请参考man和google,实现细节,请参阅linux kernel source。

你可能感兴趣的:(linux C 编程 之 socket 网络编程)