网络套接字:socket 在通信过程中,套接字一定是成对出现(listen监听除外)
Linux内核实现套接字通信: 一个文件描述符,两个缓冲区
一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现)
网络字节序:
补充知识:数据在内存中存储方式:大端法(高位低地址、低位高地址)、小端法(高位高地址,低位低地址)
网络数据流应采用大端法字节序
网络字节序与主机字节序的转换:
h:本地 n:网络 l:IP s:port(端口)
htonl:本地 ==> 网络(IP)
htons:本地 ==> 网络(PORT)
ntohl:网络 ==> 本地(IP)
ntohs:网络 ==> 本地(PORT)
IP 地址转换函数:点分十进制 == > 网络字节序 man inet_ntp可查看
//manpage函数原型说明
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
参数:
af: AF_INET、AF_INET6
src: 传入参数,IP地址(点分十进制)
dst: 传出参数,转换后的网络字节序的IP地址
socket模型创建流程图
客户端和服务器一共有三个套接字(包含一个listen套接字)
服务器端四个函数依次来看:
socket:创捷一个socket
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
函数原型:int socket(int domain, int type, int protocol);
参数:
domain:常用:
AF_UNIX Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
type: 常用:
SOCK_STREAM 流式 TCP
SOCK_DGRAM 报式 UDP
protocol:
0
返回值:
成功:新套接字所对应的文件描述符
失败:-1,并设置errno
bind:给socket绑定一个地址结构
#include <sys/types.h>
#include <sys/socket.h>
函数原型:int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
结构体:struct sockaddr *addr 通常使用 struct sockaddr_in addr 来替代
struct sockaddr_in addr:使用 man 7 ip 可以查看结构体成员变量
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 */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
参数:
sockfd:
socket函数返回值
addr:
(struct sockaddr *)&addr 需要强制类型转换
addrlen:
sizeof(addr)
返回值:
成功:0
失败:-1 设置errno
listen:设置同时与服务器建立连接的上限数(同时进行3次握手的客户端数量)
#include <sys/types.h>
#include <sys/socket.h>
函数原型:int listen(int sockfd, int backlog);
参数:
backlog(最大不超过128):上限数值
accept:阻塞等待客户端建立连接,成功的话返回一个与客户端成功连接的sockfd
#include <sys/types.h>
#include <sys/socket.h>
函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:addr:传出参数,成功与服务器建立连接的那个客户端的地址结构
socklen_t client_addr_len = sizeof(addr);
addrlen:传入传出参数
&client_addr_len
返回值:
成功:能与服务器进行数据通信的socket对应的文件描述符
失败:-1, errno
TCP serve代码实现:
#define SERV_PORT 9999
int main(int argc, char *argv[])
{
int lfd, cfd;
int ret;
int i;
char buf[BUFSIZ], clint_IP[1024]; //BUFSIZ:8192
struct sockaddr_in serv_addr, clit_addr;
socklen_t clit_addr_len;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = Socket(AF_INET, SOCK_STREAM, 0);
Bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
Listen(lfd, 128);
clit_addr_len = sizeof(clit_addr);
cfd = Accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len);
printf("clint ip:%s port:%d\n",
inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, clint_IP,
sizeof(clint_IP)),ntohs(clit_addr.sin_port));
/*
实现客户端发送数据 服务器进行大小写转换 最后输出给客户端 打印在屏幕上
*/
while(1){
ret = read(cfd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, ret);
for(i = 0; i< ret; i++) {
buf[i] = toupper(buf[i]);
}
write(cfd, buf, ret);
}
return 0;
}
客户端函数模型
connect:与客户端建立连接
#include <sys/types.h>
#include <sys/socket.h>
函数原型:int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
在客户端之所以没有bind就可以和服务器建立连接
原因是因为系统采用“隐式绑定”
TCP client代码实现:
int main(int argc, char *argv[])
{
int cfd;
int conter = 10;
char buf[BUFSIZ];
int ret;
struct sockaddr_in serv_addr; //server ip + port
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(9999);
inet_pton(AF_INET, "127.1", &serv_addr.sin_addr.s_addr);
cfd = Socket(AF_INET, SOCK_STREAM, 0);
ret = Connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if(ret == -1) {
exit(1);
}
while(conter--) {
write(cfd, "hello\n", 6);
sleep(1);
ret = read(cfd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, ret);
}
close(cfd);
return 0;
}
在这里我封装了出错处理函数 如需要函数源码可以私信我
TCP流程分析
serve client
socket() socket()
bind()
listen()
accept() connect()
read() write()
toupper() read()
write() 显示读取结果
close() close()