1、相关基础
tcp协议:面向连接的,一对一的可靠通信
可靠性:严格的应答机制
每一个数据包都有一个对应的编号
(1)端口:
标识进程 无符号短整型:0~65535 01024被内核使用。用户可指定端口:102465535
字节库:大端库 小端库
网络字节序通常是大端序:大端就是指地址存放高字节
个人pc的字节序通常是小端序:小端就是指地址存放高字节
结论:需要在网络绑定port端口是进行字节序的转换
eg:
#include <stdio.h>
int main()
{
int a=0x11223344;
char *p=(char *)&a;
printf("%p %#x\n",p,*p);
printf("%p %#x\n",p+1,*(p+1));
printf("%p %#x\n",p+2,*(p+2));
printf("%p %#x\n",p+3,*(p+3));
return 0;
}
输出:
如图,低字节44存放在FE14低地址中,高字节11存放在FE17高地址中,这就是小端序,我们大部分系统都是小端序。
(2)客户端与服务器的连接(握手)与断开(挥手)
ACK: 应答
SYN:请求
FIN:请求断开
2、TCP服务器和客户端的搭建
tcp流程 服务器:socket() --> bind() --> listen() --> accept() --> read/write or recv/send -->close()
客户端:socket() --> connect() --> read/write or recv/send -->close()
这里只介绍两个函数,其他函数说明因为说过了,可以看之间的博客。
recv()—接收
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
send()—发送
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
3、实例说明
(1).客户端给服务器发送一个字符串,服务器返回给客户端这个字符串的长度 sprintf(buf, “%d”, strlen(buf));
(2).实现一个时间服务器,客户端发送time,服务器返回当前时间。
time_t val = time(NULL); char *p = ctime(&val); char data[64] = {0}; strcpy(data, p);
(3).如果客户端发送get 1.txt的请求,服务器获取文件内容后,发送给客户端。
(4)服务器与客户端的交互
代码演示:
1、TCP服务器:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
void *send_data(void *arg)//子线程
{
int connfd=*(int *)arg;
char buf[64]={0};
while(1)
{
fgets(buf,64,stdin);//获取字符串
buf[strlen(buf)-1]='\0';去除换行符
send(connfd,buf,strlen(buf),0);发送到客户端
}
close(connfd);//关闭
}
char *getTime(char *buf)//获取时间
{
struct tm *t_cur;//定义tm结构体,便于自己能得到想要的时间戳格式
time_t t;
memset(buf,0,sizeof(buf));//清空缓冲区
time(&t);
t_cur=localtime(&t);
sprintf(buf,"%d年/%d月/%d日 %d:%d:%d\n",t_cur->tm_year+1900,t_cur->tm_mon+1,t_cur->tm_mday,t_cur->tm_hour,t_cur->tm_min,t_cur->tm_sec);//将自己想要的时间戳格式输入到buf里
return buf;
}
int main(int argc, char *argv[])
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);//创建套接字
if(sockfd<0)
{
perror("socket");
return -1;
}
//初始化地址和端口
struct sockaddr_in ser={0};
int len=sizeof(ser);
ser.sin_family=AF_INET;
ser.sin_port=htons(9898);
ser.sin_addr.s_addr=htonl(INADDR_ANY);//ser.sin_addr.s_addr=inet_addr("0");自动路由,寻找ip
int on=1;
//端口复用函数,为了解决端口号被系统占用的情况
int res=setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if(res==-1)
{
perror("setsockopt");
return -1;
}
int ret=bind(sockfd,(struct sockaddr *)&ser,len);//绑定套接字
if(ret<0)
{
perror("bind");
return -1;
}
ret=listen(sockfd,10);//监听套接字
if(ret<0)
{
perror("bind");
return -1;
}
struct sockaddr_in clientaddr={0};
int n=sizeof(clientaddr);
while(1)
{
printf("wait for the client\n");
//等待客户端连接
//建立连接请求
int connfd=accept(sockfd,(struct sockaddr *)&clientaddr,&n);
if(connfd<0)
{
perror("accept");
return -1;
}
printf("client ip:%s client port:%d\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
pthread_t pid;//创建子进程,进行双向交互
pthread_create(&pid,NULL,send_data,(void *)&connfd);
pthread_detach(pid);
//收发数据(重点)
char buf[64]={0};
while(1)
{
int m=recv(connfd,buf,64,0);//接收数据
if(m<0)
{
perror("read");
return -1;
}
else if(m==0)//客户端关闭了
{
close(connfd);
printf("客户端已关闭 请重新连接客户端!\n");
break;
}
else
{
if(strcmp(buf,"time\n")==0)//匹配时间戳
{
sprintf(buf,"%s",getTime(buf));
printf("rev success\n");
send(connfd,buf,sizeof(buf),0);
memset(buf,0,64);//buf清零
}
else if(strcmp(buf,"get 1.txt\n")==0)//获取文件内容
{
char data[64]={0};
FILE *fp=fopen("1.txt","r");
fread(data,sizeof(data),1,fp);
send(connfd,data,strlen(data),0);
}
else
{
printf("%s",buf);//除了上面两种情况,输出
sprintf(buf," %ld",strlen(buf)-1);
printf("rev success\n");
send(connfd,buf,sizeof(buf),0);
memset(buf,0,64);//buf清零
}
memset(buf,0,64);
}
}
}
close(sockfd);//关闭套接字
return 0;
}
TCP客户端//除了流程外,代码意思大致相同,就不说明了
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
void *recv_data(void *arg)
{
int sockfd=*(int *)arg;
char buf[64]={0};
while(1)
{
int n=recv(sockfd,buf,sizeof(buf),0);
if(n<=0)
{
perror("pid read");
return NULL;
}
printf("%s",buf);
}
//close(sockfd);
}
int main(int argc, char *argv[])
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
perror("socket");
return -1;
}
struct sockaddr_in clientaddr={0};
int len=sizeof(clientaddr);
clientaddr.sin_family=AF_INET;
clientaddr.sin_port=htons(9898);
clientaddr.sin_addr.s_addr=htonl(INADDR_ANY);//0.0.0.0
int ret=connect(sockfd,(struct sockaddr *)&clientaddr,len);
if(ret<0)
{
perror("connect");
return -1;
}
pthread_t pid;
pthread_create(&pid,NULL,recv_data,(void *)&sockfd);
pthread_detach(pid);
//收发数据(重点)
char buf[64]={0};
//char str[64]={0};
while(1)
{
fgets(buf,64,stdin);//获取输入的字符串
if(strcmp(buf,"time\n")==0)
{
send(sockfd,buf,strlen(buf),0);
memset(buf,0,64);//buf清零
recv(sockfd,buf,64,0);
printf("nowtime is %s",buf);
memset(buf,0,64);
}
else if(strcmp(buf,"get 1.txt\n")==0)
{
recv(sockfd,buf,64,0);
printf("%s",buf);
memset(buf,0,64);
}
else
{
send(sockfd,buf,strlen(buf),0);
memset(buf,0,64);//buf清零
recv(sockfd,buf,64,0);
printf("sizeof is %s\n",buf);
memset(buf,0,64);
}
}
close(sockfd);
return 0;
}
1.txt内容:
运行结果;