socket编程API——学习记录
服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
服务器为socket绑定ip地址和端口号
服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开
客户端创建socket
客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket
服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端谅解请求:
客户端连接成功,向服务器发送连接状态信息
服务器accept方法返回,连接成功
客户端向socket写入信息
服务器读取信息
客户端关闭
服务器端关闭
如何唯一标识网络中的一个进程 :
TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。
socket();
int socket(int domain, int type, int protocol);
/例子/
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
函数功能:根据指定的地址族、数据类型和协议来分配一个socket的描述字及其所用的资源。
domain:
协议族,常用的有AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE其中AF_INET代表使用ipv4地址
type:
socket类型,常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等
protocol:
协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等 (可以是“0”表示自动识别协议)
返回值: sockfd 成功 //返回这个套接字的文件描述符 sockfd
-1 失败
bind():
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函数功能:把一个地址族中的特定地址赋给socket
sockfd:
服务器的socket描述字,也就是socket的引用 。// sockfd -> server_fd
addr:
要绑定给sockfd的协议地址(服务器/端的地址) //addr -> server_addr
addrlen:
地址的长度 socklen_t addrlen
的常用形式:sizeof(struct sockaddr)
返回值: 0 绑定成功
-1 绑定失败
(1) 通用套接字数据结构(它可以在不同的协议族之间进行强制转换)
/*套接字地址结构,常用于bind、connect、recvfrom、sendto等函数的参数*/
struct sockaddr{
sa_family_t sa_family; /*协议族*/
char sa_data[14]; /*协议族数据*/
};
/*as_family_t类型是unsigned short类型,因此成员变量sa_family是16字节*/
typedef unsigned short sa_family_t;
(2) 以太网中地址结构:struct sockaddr_in (in表示internet,网络地址)
/*Internet以太网套接字地址结构*/
struct sockaddr_in {
short int sin_family; /*通信协议,如:AF_INET(IPV4),通常与socket函数的domain一致*/
unsigned short int sin_port; /*2字节,16位的端口号,网络字节序*/
struct in_addr sin_addr; /*IP地址,32位*/
unsigned char sin_zero[8]; /*未用*/
}
struct in_addr{
unsigned long s_addr; /*32位IP地址,网络字节序*/
};
/常用地址设置方法/
define MYPORT 3490 /端口地址/
struct sockaddr_in my_addr; /以太网套接字地址结构/
my_addr.sin_family = AF_INET; /地址结构的协议族/
my_addr.sin_port = htons(MYPORT); /端口地址转换为网络字节序/
my_addr.sin_addr.s_addr = inet_addr(“192.168.1.150”); /将字符串形式的IP地址转换为网络字节序/
~~~
注:sockaddr 和 sockaddr_in的相互关系
一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数
- sockaddr_in用于socket定义和赋值
- sockaddr用于函数参数
网络字节顺序 (Network Byte Order) NBO
结构体的sin_port和sin_addr都必须是NBO
本机字节顺序 (Host Byte Order) HBO
一般可视化的数字都是HBO
(3) NBO与HBO之间的转换(网络字节序与主机字节序之间的转换)
inet_addr() 将字符串点数格式地址转化成无符号长整型(unsigned long s_addr s_addr;)
inet_aton() 将字符串点数格式地址转化成NBO
inet_ntoa () 将NBO地址转化成字符串点数格式
htons() "Host to Network Short"
htonl() "Host to Network Long"
ntohs() "Network to Host Short"
ntohl() "Network to Host Long"
常用的是htons(),inet_addr()正好对应结构体的端口类型和地址类型
/*IPV4对应地址结构*/
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */ 如:my_addr.sin_addr.s_addr = inet_addr("192.168.1.150")
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order .32位IP地址,网络字节序*/
};
/*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 */
};
Unix域对应的地址结构
#### #define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
listen();
int listen(int sockfd, int backlog);
函数功能:监听socket (服务端创建的socket)
sockfd: 要监听的socket描述字,服务端socket产生的描述符 sockfd -> server_sockfd
backlog:相应socket可以排队的最大连接个数
返回值: 0 成功
-1 失败
connect();
int connect(int sockfd, const struct sockaddr *addr, socklen_t* addrlen);
函数功能:连接某个socket
sockfd: 客户端 的socket描述符 sockfd -> client_sockfd
addr: 服务器 的socket地址(addr -> server_addr
包含要连接的服务器的 IP+端口) 连接到地址结构指定的服务器上
addrlen:是一个指针不是结构, socket地址的长度(第二个参数地址结构的长度)socklen_t addrlen
=> sizeof(server_addr)
accept();
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
/*例子*/
int server_sockfd, client_sockfd;
struct sockaddr_in client_addr; /*客户端地址*/
int addr_length; /*用于保存网络地址长度*/
addr_length = sizeof(struct sockaddr_in); /*地址长度*/
client_sockfd = accept(servr_sockfd, &client_addr, &addr_length ); /*等待客户端连接,地址在client_addr中*/
TCP服务器监听到客户端请求之后,调用accept()函数取接收请求 返回值:client_sockfd
sockfd:服务器的socket描述字 sockfd -> server_sockfd
称为监听描述符
addr:客户端的socket地址 addr -> client_addr
addrlen:socket地址的长度
返回值: client_fd 成功,返回新连接的客户端套接字文件描述符 函数send()和recv()通过这个文件描述符进行数据收发
-1 失败
网络I/O操作有下面几组:read()/write() 、recv()/send()、readv()/writev()、recvfrom()/sendto()、recvmsg()/sendmsg()
- read()/write()
- recv()/send()
- readv()/writev()
- recvmsg()/sendmsg()
- recvfrom()/sendto()
/*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);
#include <sys/types.h>
#include <sys/socket.h>
/*send()和recv()函数*/
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);
/*sendto()和recvfrom()函数*/
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
/*sendmsg()和recvmsg()函数*/
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
片
read();
ssize_t read(int fd, void *buf, size_t count);
功能:读取socket内容
fd:socket描述字
buf:缓冲区
count:缓冲区长度
返回值:成功,返回实际所读的字节数;返回“0”表示已读到的文件结束了
<0,出错 若错误为“EINTR”表示由中断引起,“ECONNREST”表示网络连接出错
write();
ssize_t write(int fd, const void *buf, size_t count);
功能:向socket写入内容,其实就是发送内容
fd:socket描述字
buf:缓冲区
count:缓冲区长度
返回值: 成功,返回写入字节数,>0,表示写入了部分或是全部的数据
<0,错误, “EINTR”表示写入时发生了中断,“PIPE”表示网络连接出错(对方已关闭连接)
send();
ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);
//flags参数值为“0”
sockfd:指定发送端套接字描述符。正在监听端口的套接字描述符
buff: 存放要发送数据的缓冲区
nbytes: 实际要改善的数据的字节数大小
flags: 一般设置为0
返回值: 成功,返回发送数据的长度,可以是0
-1,失败
flags参数
flags | 说明 | recv | send |
---|---|---|---|
MSG_DONTROUTE | 绕过路由表查找 | • | |
MSG_DONTWAIT | 仅本操作非阻塞 | • | • |
MSG_OOB | 发送或接收带外数据 | • | • |
MSG_PEEK | 窥看外来消息 | • | |
MSG_WAITALL | 等待所有数据 | • |
recv();
ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);
sockfd: 接收端套接字描述符
buff: 用来存放recv函数接收到的数据的缓冲区
nbytes: 指明buff的长度
flags: 一般置为0
close();
int close(int fd);
服务器端:close(client_sockfd); //关闭客户端连接
socket标记为以关闭 ,使相应socket描述字的引用计数-1,当引用计数为0的时候,触发TCP客户端向服务器发送终止连接请求。
服务器地址清零:bzero() && memset()
bzero()
void bzero(void *s, int n);
参数:s为内存(字符串)指针,n 为需要清零的字节数。
bzero()会将参数s 所指的内存块(字符串)前n 个字节,全部设为零值。
bzero(void *s, int n);
《=等价于=》 memset((void*)s, 0,size_t n);
memset();—— (memset()函数的作用是清空数组)
void *memset(void *buffer, int c, int count);
或:void *memset(void *s, char ch, unsigned n);
void *memset(void *s,int c,size_t n);
buffer为指针或是数组,c是赋给buffer的值,count是buffer的长度.
这个函数在socket中多用于清空数组.
如:原型是memset(buffer, 0, sizeof(buffer))
注:不一定就是把内容全部设置为ch指定的ASCII值,而且该处的ch可为int或者其他类型,并不一定要是char类型。
1)void *memset(void *s,int c,size_t n)
总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。
2).memset() 函数常用于内存空间初始化。如:
char str[100];
memset(str,0,100); //全都初始化为0
3).memset可以方便的清空一个结构类型的变量或数组。
如:
struct sample_struct{
char csName[16];
int iSeq;
int iType;
};
//对于变量:
struct sample_strcut stTest;
//一般情况下,清空stTest的方法:
stTest.csName[0]='/0';
stTest.iSeq=0;
stTest.iType=0;
//用memset就非常方便:
memset(&stTest, 0, sizeof(struct sample_struct)); //初始化结构体
//如果是数组:
struct sample_struct TEST[10];
则
memset(TEST, 0, 10*sizeof(struct sample_struct)); //初始化数组
memset()函数,例子
#include <string.h>
#include <stdio.h>
#include <memory.h>
int main(void)
{
char buffer[] = "Hello world/n";
printf("Buffer before memset: %s/n", buffer);
memset(buffer, '*', strlen(buffer) ); //数组直接首地址传进去。 主要是对数组指针的修改!!因为可以被修改而const char int等这些 //不能被修改 和malloc 配套使用
printf("Buffer after memset: %s/n", buffer);
return 0;
}
输出结果:
Buffer before memset: Hello world
Buffer after memset: ***********