基础
地址
地址由IP地址、端口号构成
IP地址:用于设备标识
端口号:用于标识网络服务(ftp、http、socket) (5000到10000之间选择)
IP地址转换API(IP地址是字符串要转换成网络能识别格式)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
//字符串转网络字节序
//第一个参数为字符串(ip地址)第二个参数是结构体
char *inet_ntoa(struct in_addr in); //网络字节序转字符串
字节序转换API (端口号: 主机字节序转换成网络字节序)
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); //将无符号整数hostlong从主机字节顺序转换为网络字节顺序
uint16_t htons(uint16_t hostshort); //将无符号短整数hostshort从主机字节顺序转换为网络字节顺序
uint32_t ntohl(uint32_t netlong); //将无符号整数netlong从网络字节顺序转换为主机字节顺序
uint16_t ntohs(uint16_t netshort); //将无符号短整数netshort从网络字节顺序转换为主机字节顺序
h --> host(主机) n --> net(网络) s --> short l --> long
socket传输的过程
socket常用API
socket()函数
功能:获取套接字
函数原型
//#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol)
参数:
domain
指定所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)
AF_INET IPv4 因特网
AF_INET6 IPv6 因特网
AF_UNIX Uinix域
AF_ROUTE 路由套接字
AF_KEY 密钥套接字
AF_UNSPEC 未指定
type
指定socket类型:
SOCK_STREAM 指定TCP协议
字节流套接字提供可靠的、面向连接的通信流;他使用TCP协议,从而保证了数据传输正确性和顺序性
SOCK_DGRAM 指定UDP协议
数据报文套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,使用UDP协议
SOCK_RAW 指定底层协议
允许程序使用底层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用不便,主要用于一些协议的开发- 项目
protocol
通常赋值为0(让它3自动分配)
0 选择type类型的默认协议TCP协议
IPPROTO_TCP TCP传输协议
IPPPOTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议
IPPROTO_TIPC TIPC传输协议
bind()函数
功能:绑定IP号和端口号到socket_fd
函数原型
//#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
参数
sockfd
是一个socket描述符
addr
是指向一个结构为sockaddr参数的指针,sockaddr中包含了地址、端口和IP地址的信息。在进行地址绑定的时候,需要将地址结构中的IP地址、端口、类型等结构struct sockaddr中的域进行设置之后才能进行绑定,这样进行绑定后才能将套接字文件描述符与地址等接合在一起。
addrlen
结构体add的大小
返回值
成功返回0 ,失败返回-1,errno被设置
struct sockaddr ----IPv4
struct sockaddr {
sa_family_t sa_family; //协议族
char sa_data[14]; //IP地址+端口号
};
常用struct sockaddr_in
类型强转替换struct sockaddr
struct sockaddr_in {
__kernel_sa_family_t sin_family; //协议族
__be16 sin_port; //端口号
struct in_addr sin_addr; //IP地址结构体
/* 填充 只为了跟sockaddr结构体对齐才能相互转换 */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
struct in_addr {
__be32 s_addr;
};
如何查找struct sockaddr_in
?
1.进入头文件文件夹cd /usr/include/
2.搜索当前文件夹下显示行数不区分大小写递归查找grep 'struct sockaddr_in {' * -nir
n显示行数,i不区分大小写,r 递归的(包括子目录) *表示当前目录的所有文件
3.进入头文件查看vi linux/in.h +184
+184表示转到第184行
示例
如果当前目录有其他类型文件,如下图中的a.out,file文件会影响搜索结果,所以需要用*.c
来区分开
listen()函数
功能:listen函数使用主动连接套接字变为被连接套接口,使得一个进程可以接受其它进程的请求,
从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
listen函数一般在调用bind之后-调用accept之前调用。
函数原型
//#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数
sockfd
进行监听的套接字
backlog
指定在请求队列中允许的最大请求数
返回值
成功返回0 ,失败返回-1,errno被设置
accept()函数
功能:与客户端建立连接,如果已完成请求的队列为空将阻塞到下一个请求到来。
接受一个客户端的连接请求,并返回一个新的套接字。所谓“新的”就是说这个套接字与socket()返回的用于监听和接受客户端的连接请求的套接字不是同一个套接字。与本次接受的客户端的通信是通过在这个新的套接字上发送和接收数据来完成的。
函数原型
//#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd
监听的套接字
addr
(可选择不使用填NULL)
用于保存已连接的客户端的地址
addrled
(可选择不使用填NULL)
客户端地址长度
返回值
返回一个已连接的新的套接字,交互完成后该套接字就会被关闭。而第一个参数是服务器监听接字描述行。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。
失败返回-1,errno被设置。
数据收发函数
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);
2.recv()、send()
已连接状态使用
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
使用与read、write类似, flags控制选项常设置为0
3.readv()、wirtev()
已连接状态使用
#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
struct iovec {
void *iov_base; /*指向一个缓冲区,这个缓冲区是存放readv()所接收的数据或 //writev()将要发送的数据*/
size_t iov_len; /*接收的最大长度以及实际写入的长度*/
};
//iovcnt 为iov大小
4.recvmsg()、sendmsg()
未连接状态可使用
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
struct msghdr {
void *msg_name; /* optional address */
socklen_t msg_namelen; /* size of address */
struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* ancillary data, see below */
size_t msg_controllen; /* ancillary data buffer len */
int msg_flags; /* flags on received message */
};
5.recvfrom()、sendto()
未连接状态可使用
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
1.2.3多用于TCP
4.5多用于 UDP
connect()函数
功能:向服务端发出连接请求并进行连接
函数原型
//#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
sockfd
与服务端连接的套接字
addr
服务端IP地址与端口号地址结构体指针
addrlen
常设置为 sizeof(struct sockaddr)
返回值
成功返回0 ,失败返回-1,errno被设置
示例
服务端
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include<linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include<stdlib.h>
#include<string.h>
int main(int argc,char *argv[])
{
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
int n_read;
int c_fd;
int mark=0;
char *readBuf=NULL;
readBuf=(char*)malloc(128);
char *msg=NULL;
msg=(char*)malloc(128);
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
int s_fd=socket(AF_INET,SOCK_STREAM,0);
if(s_fd==-1){
perror("aocket");
exit(-1);
}
//2.bind
s_addr.sin_family=AF_INET;
s_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&s_addr.sin_addr);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.listen
listen(s_fd,4);
//4.accept
int n_connect=sizeof(struct sockaddr_in);
while(1){//不断的检测客户端有没有连接
c_fd=accept(s_fd,(struct sockaddr*)&c_addr,&n_connect);
mark++;
if(c_fd==-1){
perror("accept");
}
if(fork()==0){//创建一个子进程来连接客户端
printf("connect ip is :%s\n",inet_ntoa(c_addr.sin_addr));
//5.read
if(fork()==0){//创建一个子进程来不断的读
while(1){
memset(readBuf,0,128);
n_read=read(c_fd,readBuf,128);
if(n_read==-1){
perror("read");
} else{
printf("get msg:%d %s\n",n_read,readBuf);
}
}
}
while(1){//不断的发送信息回去给客户端
//6.write
memset(msg,0,128);
sprintf(msg,"welcome NO.%d client",mark);
write(c_fd,msg,strlen(msg));
sleep(3);
}
exit(1);
}
}
return 0;
}
客户端
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include<stdlib.h>
#include<string.h>
int main(int argc,char *argv[])
{
int c_fd;
struct sockaddr_in c_addr;
char *msg=NULL;
char *readBuf=NULL;
int n_read;
readBuf=(char *)malloc(128);
msg=(char *)malloc(128);
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
c_fd=socket(AF_INET,SOCK_STREAM,0);
if(c_fd==-1){
perror("socket");
}
//2.connect
c_addr.sin_family=AF_INET;
c_addr.sin_port=htons(atoi(argv[2]));
inet_aton(argv[1],&c_addr.sin_addr);
connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr));
//3.write
if(fork()==0){
while(1){//创建一个子进程来不断的写信息给服务端
memset(msg,0,128);
printf("client imput:");
gets(msg);
write(c_fd,msg,strlen(msg));
}
}
//4.read
while(1){//不断的读来自服务端的信息
memset(readBuf,0,128);
n_read=read(c_fd,readBuf,128);
if(n_read==-1){
perror("read");
}else{
printf("from server:%d %s\n",n_read,readBuf);
}
}
return 0;
}
结果