目录
二、概念及函数
1.套接字描述符
2.socket函数
3.bind函数
4.connect函数
5.listen函数
6.accept函数
7.close函数
四、笔记
1.一个标准的三次握手抓包
2.小技巧:打印出connect过程的错误
一、流程简述
服务器端
创建套接字(socket) -> 绑定socket和端口号(bind) -> 监听该端口(listen) -> 接收来自客户端的连接请求(accept) -> 读写(recv\read...) -> 关闭(close)
客户端
创建套接字(socket) -> 连接指定计算机的端口(connect) -> 读写(recv\read...) -> 关闭(close)
二、概念及函数
1.套接字描述符
套接字是通信端点的抽象,两个端口之间通过套接字建立连接进行通信,通过socket函数可以创建一个套接字,函数返回值为套接字描述符,套接字描述符有点类似文件描述符,也可以通过read和write函数对其读写操作。
2.socket函数
头文件 #include <sys/socket.h>
函数体 int socket (int domain, int type, int protocol);
返回值 成功返回套接字描述符,出错返回-1
domain 为域,用来分辨地址类型,常用就是AF_INET 和AF_INET6,如果用AF_UPSPEC则protocol参数需要指定
AF_INET ipv4因特网域
AF_INET6 ipv6因特网域
AF_UNIX UNIX域
AF_UPSPEC 未指定
type用来确定套接字类型,通畅udp选择SOCK_DGRAM ,tcp选择SOCK_STREAM
SOCK_DGRAM 固定长度无连接的不可靠报文传递
SOCK_RAW ip协议的数据报接口
SOCK_SEQPACKET 固定长度的,有序的,可靠的,面向连接的报文传递
SOCK_STREAM 有序的,可靠的,双向的,面向连接的字节流
protocol通常是0,表示为给定的域domain参数和套接字类型type参数选择默认协议,在AF_INET 域中,类型为SOCK_STREAM的默认协议为IPPROTO_TCP。在AF_INET 域中,类型为SOCK_DGRAM 的默认协议为IPPROTO_UDP。尾缀就代表的协议
IPPROTO_IP
IPPROTO_IPV6
IPPROTO_ICMP
IPPROTO_RAW
IPPROTO_TCP
IPPROTO_UDP
3.bind函数
头文件 #include <sys/socket.h>
函数体 int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
返回值 成功返回0,出错返回-1
sockfd 为套接字描述符
addr 为指向地址结构的指针
len 为地址结构的长度
ipv4的地址结构用sockaddr_in表示
typedef uint16_t in_port_t ;
typedef uint32_t in_addr_t;
struct in_addr{
in_addr_t s_addr;
};
struct sockaddr_in{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
};
4.connect函数
头文件 #include <sys/socket.h>
函数体 int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
返回值 成功返回0,出错返回-1
参数同bind函数
注意:如果是非阻塞状态下,调用connect函数会直接返回-1,错误代码115
5.listen函数
头文件 #include <sys/socket.h>
函数体 int listen(int sockfd, int backlog);
返回值 成功返回0,出错返回-1
backlog规定为连接请求(connect)队列的最大个数,注意不是tcp连接的最大数量,是未完成连接的最大请求数量。
6.accept函数
头文件 #include <sys/socket.h>
函数体 int accept(int sockfd, struct sockaddr *addr, socklen_t *len);
返回值 成功返回一个新的套接字描述符(原来的套接字描述符可以继续用于accept函数的侦听),出错返回-1
addr 客户端地址内容
len 接收客户端地址内容的长度
如果不关心客户端的标识(地址),可以将addr和len设为NULL,不然需要设置足够的空间来存储这些数据。
如果没有连接请求,accept会阻塞直到一个请求的到来。如果socket设置为非阻塞模式,会直接返回-1。
7.close函数
头文件 #include <unistd.h>
函数体 int close(int fd);
返回值 成功返回0,出错返回-1
三、代码
一个特别特别简单的tcp连接代码,为了熟悉这个流程
注意htons,htonl,ntohs,inet_pton,inet_ntop等函数的使用
服务端
/*server*/
#include<stdio.h>
#include<unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
/*
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
struct in_addr{
in_addr_t s_addr;
};
struct sockaddr_in{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
};*/
int main()
{
int sockfd = 0;
int newsockfd = 0;
int ret = 0;
unsigned int addr=0;
unsigned int client_sock_len=sizeof(struct sockaddr_in);
struct sockaddr_in sock;
struct sockaddr_in client_sock;
char send_str[]="Hello client!\n";
char recv_str[100];
char client_addr_str[15];
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
printf("socket error\n");
return 0;
}
sock.sin_family = AF_INET;
sock.sin_port = htons(2000);//必须使用htons转换
/*将字符串的ip地址转化为网络字节序的整形*/
if(inet_pton(AF_INET,"192.168.206.128",(void *)&addr)!= 1)
{
printf("inet_pton addr error\n");
return 0;
}
sock.sin_addr.s_addr = addr;
ret = bind(sockfd,(struct sockaddr *)&sock,sizeof(struct sockaddr_in));
if(ret < 0)
{
printf("bind error\n");
return 0;
}
ret = listen(sockfd,5);
if(ret < 0)
{
printf("listen error\n");
return 0;
}
newsockfd = accept(sockfd,(struct sockaddr *)&client_sock,&client_sock_len);
if(newsockfd < 0)
{
printf("accept error\n");
return 0;
}
/*将网络字节序的整形转化为字符串格式的ip地址*/
if(inet_ntop(AF_INET,(void *)&(client_sock.sin_addr),client_addr_str,sizeof(client_addr_str)) == NULL)
{
printf("inet_ntop addr error\n");
return 0;
}
printf("newsockfd = %d,oldsockfd = %d\n",newsockfd,sockfd);
/*客户端的ip和端口号*/
printf("client connect success\n address = %s,port = %d \n",client_addr_str,ntohs(client_sock.sin_port));
/*发送数据*/
if(send(newsockfd,send_str,sizeof(send_str),0) == -1)
{
printf("send error\n");
return 0;
}
/*接收数据*/
if(recv(newsockfd,recv_str,sizeof(recv_str),0) > 0)
{
printf("S recv:%s\n",recv_str);
}
close(sockfd);
close(newsockfd);
return 0;
}
客户端
/*client*/
#include<stdio.h>
#include<unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
int main()
{
int sockfd = 0;
int ret = 0;
struct sockaddr_in sock;
unsigned int addr=0;
char send_str[]="Hello service!\n";
char recv_str[100];
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
printf("socket error\n");
return 0;
}
sock.sin_family = AF_INET;
sock.sin_port = htons(2000);
if(inet_pton(AF_INET,"127.0.0.1",&addr)!= 1)
{
printf("inet_pton addr error\n");
return 0;
}
sock.sin_addr.s_addr = addr;
ret = connect(sockfd,(struct sockaddr_in *)&sock,sizeof(struct sockaddr_in));
if(ret < 0)
{
printf("connect error=%d %s\n",errno,strerror(errno));//这个可以打印出错误码和原因
return 0;
}
if(send(sockfd,send_str,sizeof(send_str),0) == -1)
{
printf("send error\n");
return 0;
}
if(recv(sockfd,recv_str,sizeof(recv_str),0) > 0)
{
printf("C recv:%s\n",recv_str);
}
close(sockfd);
return 0;
}
四、笔记
1.一个标准的三次握手抓包
第一次握手:客户端发送syn包(syn=j)到服务器。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个ASK包(ask=k)。
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1)。
三次握手完成后,客户端和服务器就建立了tcp连接,这时可以调用accept函数获得此连接
2.小技巧:打印出connect过程的错误
#include <errno.h>
#include <string.h>
printf("connect error=%d %s\n",errno,strerror(errno));