TCP通信
Socket 起源于 Unix,而Unix基本哲学之一就是一切皆文件,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符,下图就是基于C/S架构的tcp通信模型
server的生命周期:
- 创建socket
- 绑定地址,端口
- 监听网络连接
- 接收连接
- 关闭连接
clinet的生命周期
- 创建socket
- 绑定地址
- 发起连接
- 关闭连接
server服务器端
首先需要使用sockaddr_in来定义地址
struct sockaddr_in {
sa_family_t sin_family; //地址族(Address Family)
uint16_t sin_port; //16位TCP/UDP端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero[8] //清0操作
};
//另一个结构体
struct in_addr {
In_addr_t s_addr; //32位IPv4地址
};
sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作.
使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数。
1.socket()
socket()用来创建一个socket,
int socket(int domain,int type, int protocol)
返回值非负:成功 ;-1 : 失败
参数一 通常有 AF_INET, AF_INET6, AF_LOCAL等
参数二 是接口类型,就是流 TCP是SOCK_STREAM,UDP则是SOCK_DGRAM其他用法可以man一下查找
参数三 是和上面相匹配的,写0会自动匹配。
bind()
int bind(int sockfd,const struct sockaddr* myaddr,socklen_t addrlen)
返回值为0:成功;-1:失败
当socket函数返回一个描述符时,只是存在于其协议族的空间中,并没有分配一个具体的协议地址(这里指IPv4/IPv6和端口号的组合),bind函数可以将一组固定的地址绑定到sockfd上。
参数一是之前定义的socket描述符
参数二制定了ip和端口号,在声明结构体之后通过(struct sockaddr *)&mysock就可以当参数
参数三是和前面结构体的长度相同的。
通常服务器在启动的时候都会绑定一个众所周知的协议地址,用于提供服务,客户就可以通过它来接连服务器;而客户端可以指定IP或端口也可以都不指定,未分配则系统自动分配。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。
listen()
int listen(int sockfd,int backlog)
返回值:0:成功;-1:失败
参数一:socket描述符
参数二:最大连接数量(还有更规范的定义需要再查)
accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
返回值 非负:成功;-1:失败
参数和bind()一样
一般来说,实现时accept()为阻塞函数,当监听socket调用accept()时,它先到自己的receive_buf中查看是否有连接数据包;若有,把数据拷贝出来,删掉接收到的数据包,创建新的socket与客户发来的地址建立连接;若没有,就阻塞等待;
recv()
ssize_t recv(int sockfd,void *buf, size_t len,int flags)
参数一:接收套接字描述符
参数二:指定缓冲区地址,接收储存数据
参数三:接收数据缓冲区长度
参数四:一般为0
表示从接收缓冲区拷贝数据。成功时,返回拷贝的字节数,失败返回-1。阻塞模式下,recv/recvfrom将会阻塞到缓冲区里至少有一个字节(TCP)/至少有一个完整的UDP数据报才返回,没有数据时处于休眠状态。若非阻塞,则立即返回,有数据则返回拷贝的数据大小,否则返回错误-1,置错误码为EWOULDBLOCK。
send()
ssize_t send(int sockfd,constvoid *buf, size_t len,int flags)
参数一:socket描述符(非监听)
参数二:发送数据的缓存
参数三:发送的长度
参数四:一般为0
每一个tcp套接口都有一个发送缓冲区,调用send函数的过程,将内核将用户数据拷贝到tcp套接口的发送缓冲区的过程,len大于缓冲区大小则返回-1
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <malloc.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/time.h>
#define SERVER_PORT_TCP 8888
#define TCP_BACKLOG 10
/* 在sock_fd 进行监听,在 new_fd 接收新的链接 */
int sock_fd, new_fd;
int main(int argc, char *argv[])
{
/* 自己的地址信息 */
struct sockaddr_in my_addr;
/* 连接者的地址信息*/
struct sockaddr_in their_addr;
int sin_size;
struct sockaddr_in *cli_addr;
/* 创建socket*/
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket is erroe\n");
exit(1);
}
/* 主机字节顺序 */
/* 协议 */
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(SERVER_PORT_TCP);
/* 当前IP 地址写入 */
my_addr.sin_addr.s_addr = INADDR_ANY;
/* 将结构体其余的都清零 */
bzero(&(my_addr.sin_zero), 0);
/* bind绑定*/
if(bind(sock_fd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1)
{
perror("bind is error\r\n");
exit(1);
}
/*开始监听*/
if(listen(sock_fd, TCP_BACKLOG) == -1)
{
perror("Listen is error\r\n");
exit(1);
}
printf("start accept\n");
/* accept() 循环 等待客户端接入*/
while(1)
{
sin_size = sizeof(struct sockaddr_in);
if((new_fd = accept(sock_fd, (struct sockaddr*)&their_addr, (socklen_t *)&sin_size)) == -1){
perror("accept");
continue;//客户端连接后,搞一个新的套接字
}
//处理目标
ssize_t ret;
char recvbuf[512];
char *buf = "Hello! I'm server!";
while(1)
{
if ((ret = recv(new_fd, recvbuf, sizeof(recvbuf), 0)) == 1)
{
printf("recv error \r\n");
return -1;
}
printf("recv :\r\n");
printf("%s", recvbuf);//打印接收信息
printf("\r\n");
if((ret = send(new_fd, buf, strlen(buf) + 1, 0)) == -1)//发送buf到客户端
{
perror("send : ");
}
}
close(new_fd);
}
return 0;
}
client客户端
客户端首先需要建立一个socket,然后进行连接后进行操作
connect
int connect(int sockfd,conststruct sockaddr *addr, socklen_t addrlen)
返回值 :0:成功;-1:失败
通过此函数建立于TCP服务器的连接,实际是发起三次握手过程,仅在连接成功或失败后返回。参数sockfd是本地描述符,addr为服务器地址,addrlen是socket地址长度。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <malloc.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/time.h>
int sockfd;
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888
int main(int argc, char *argv[])
{
/* 连接者的主机信息 */
struct sockaddr_in their_addr;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
/* 如果socket()调用出现错误则显示错误信息并退出 */
perror("socket");
// exit(1);
}
/* 主机字节顺序 */
their_addr.sin_family = AF_INET;
/* 网络字节顺序,短整型 */
their_addr.sin_port = htons(SERVER_PORT);
their_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
/* 将结构剩下的部分清零*/
bzero(&(their_addr.sin_zero), 8);
if(connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1)
{
/* 如果connect()建立连接错误,则显示出错误信息,退出 */
perror("connect");
exit(1);
}
ssize_t ret;
char recvbuf[512];
char *buf = "hello! I'm client!";
while(1)
{
if((ret = send(sockfd, buf, strlen(buf) + 1, 0)) == -1)
{
perror("send : ");
}
if((ret = recv(sockfd, &recvbuf, sizeof(recvbuf), 0)) == -1){
return -1;
}
printf("recv :\r\n");
printf("%s", recvbuf);
printf("\r\n");
}
close(sockfd);
return 0;
}