TCP协议的编程是基于C/S模型(客户服务器模式):
客户端:主动通讯方,数据获取方 服务器:被动通讯方,数据提供方
TCP服务器端编程流程(socket编程)
1、int listenfd = socket();//listenfd是一种socket文件描述符
2、bind();//将listenfd与服务器的IP地址和选用端口号绑定,服务器的IP地址和服务程序选用的端口号也是固定的
3、listen();//启动监听,由内核监听listenfd代表的端口号,等待客户连接
4、int c = accept();//接收一个已完成连接过程的客户连接
5、recv();/send();//接收和发送数据
6、close(c);close(listenfd);//关闭连接和关闭服务器程序
这整个流程可以用一个例子来简单的理解,就好像我们去吃海底捞,listenfd就相当于门迎,bind相当于门迎去招呼你和你建立一个连接,listen海底捞这个店会监听门迎如果门迎在招呼客人海底捞内部就会知道这个消息,就会有一个专门的服务人员来为你点菜上菜等服务这个个过程就是accept。之后的数据信息交流就是recv和send,然后就是离开服务员离开海底捞这个店。
TCP客户端编程流程
1、socket();//创建socket
2、connect();//发起连接,主动与服务器建立连接
3、send();/recv();//与服务器端相反
4、close();//关闭连接
字节序转换:要学习socket需要先了解主机字节序和网络字节序,字节序分为大端字节序又称网络字节序(一个整数的高位字节存储在内存的低地址),小端字节序又称主机字节序(高位字节存储在内存的高地址),在网络通讯过程中要考虑字节序转换的问题。h 是主机字节序、n 是网络字节序、通常长整型函数 l 用来转换IP地址、短整型函数 s 用来转换端口号。
socket地址结构体:这里我们使用的是IPV4所以就只讲IPV4的socket地址结构体
创建socket
domain:告诉系统使用哪个底层协议族IPV4(PF_INET)IPV6(PF_INET6);type:指定服务类型,SOCK_STREAM(流服务)SOCK_UGRAM(数据报服务);protocol:默认协议设置为0.
命名socket:将my_addr所指的socket地址分配给未命名的sockfd文件描述符,成功返回0失败返回-1。
监听socket:socket被命名之后,还不能马上接受客户连接,使用如下系统创建一个监听队列以存放待处理的客户连接。
sockfd指定被监听的socket;backlog提示内核监听队列的最大长度。
接收连接:从监听队列中接受一个连接
sockfd是执行过listen系统调用监听的socket;addr用来获取被接受连接的远端socket地址,长度为addrlen。
发起连接:客户端需要通过该系统调用来主动与服务器建立连接
sockfd由系统调用返回一个socket;ser_addr是服务器监听的socket地址;长度为addrlen.
关闭连接:关闭该连接对应的socket
fd是待关闭的socket。
TCP数据读写
recv读取sockfd上的数据;buf、len知道度缓冲去的位置和大小;flag通常设置为0。
send往sockfd上写入数据;buf、len知道度缓冲去的位置和大小;flag为数据手发提供额外的控制。
下面是TCP的代码
ser.c
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>//创建socket头文件
#include<netinet/in.h>
#include<arpa/inet.h>//ip地址转换
#include<signal.h>
int listenfd = -1;
void sigfun(int sign)
{
close(listenfd);//关闭连接
exit(0);
}
int main()
{
signal(SIGINT,sigfun);//接受到SIGINT终端终止信号,就执行关闭连接操作
//1、创建socket
listenfd = socket(AF_INET,SOCK_STREAM,0);//创建套接字(协议族,服务类型,默认协议)
assert(listenfd != -1);//失败返回-1
//创建 服务器 客户端 ipv4 socket 地址
struct sockaddr_in ser,cli;
memset(&ser,0,sizeof(ser));
ser.sin_family = AF_INET;//地址族
ser.sin_addr.s_addr = inet_addr("127.0.0.1");//ipv4地址结构体 inet将点分十进制转化为网络字节序
ser.sin_port = htons(6000);//将short类型的主机字节序转换为网络字节序
//2、命名socket
int res = bind(listenfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res != -1);//出错IP地址出错、端口号被占用或没权限
//3、启动监听
listen(listenfd,5);
//4、接受连接
while(1)
{
int clilen = sizeof(cli);
int c = accept(listenfd,(struct sockaddr*)&cli,&clilen);
assert("c != -1");
//5、读取数据
if(c < 0)
{
printf("error\n");
continue;
}
while(1)
{
char recvbuf[128] = {0};
int n = recv(c,recvbuf,127,0);//阻塞运行,成功返回获取的数据个数
if(n <= 0)
{
printf("one client break\n");
//6、关闭连接
close(c);
break;
}
printf("%d: %s\n",c,recvbuf);
send(c,"OK",2,0);
}
}
}
cli.c
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
int main()
{
//创建sockt
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in ser;
memset(&ser,0,sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(6000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = connect(sockfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res != -1);
while(1)
{
printf("please input:");
char buff[128] = {0};
fgets(buff,128,stdin);
buff[strlen(buff) - 1] = 0;
if(strncmp(buff,"good bey",8)== 0)
{
close(sockfd);
break;
}
send(sockfd,buff,strlen(buff),0);
char recvbuff[128] = {0};
int n = recv(sockfd,recvbuff,127,0);
if(n <= 0)
{
close(sockfd);
break;
}
printf("client recv data: %s\n",recvbuff);
}
}