本篇博客将会实现TCP的编程流程,在实现TCP编程之前,先将TCP/IP的四层协议做以简单的介绍。
一、TCP/IP四层协议
TCP/IP的四层协议自底向上分别是数据链路层,网络层,运输层,应用层。很多人也有了解过OSI七层协议,同样的,自底向上分别是物理层,数据链路层,网络层,运输层,会话层,表示层,应用层。而我们主要来说一下TCP/IP四层协议。
1、数据链路层
a.数据链路层实现了网卡接口的网络驱动程序,以处理数据在物理媒介上的传输。
b.常用协议:ARP协议、RARP协议。
2、网络层
a.网络层实现数据包的选路和转发。
b.常用协议:IP协议、ICMP协议。
3、运输层
a.运输层为两台主机实现了端到端的服务。
b.常用协议:TCP协议、UDP协议、SCTP协议。
4、应用层
a.应用层负责处理应用程序的逻辑。
b.常用协议:DNS协议(当然应用层不止这一个协议,这里只说这一个)
二、使用TCP协议完成网络编程
1、完成网络编程需要客户端和服务器端,简单说一下客户端和服务器端。
客户端:请求服务
服务器端:提供服务
2、协议选择(本篇博客选择TCP,之后专门更一篇UDP的)
TCP协议:它是一种面向连接的,可靠的,流式服务。
UDP协议:它是一种无连接,不可靠的,数据报服务。
3、TCP的编程流程:
ser(服务器端):socket、bind、listen、accept、recv/send、close
cli(客户端):socket、(bind)、connect、recv/send、close
接下来说一下用到的函数
a. int socket( int domain, int type, int protocol);
domain:告诉系统选择哪个底层协议族, AF_INET是PF_INET协议族所对应的地址族.
type:选择协议 SOCK_STREAM(TCP), SOCK_UGRAM(UDP).
protocol:这个值一般我们都把它设置为0,表示使用默认协议。本来这个参数是在前两个参数的基础上,再选择一个具体的协议,但是前两个参数已经完全决定了它的值,所以这个参数一般置为0.
b. int bind(int sockfd, const struct sockaddr* addr, int addrlen )
addr:指定IP地址和端口号.
addrlen:指定IP地址和端口号的长度.
c. int listen(int sockfd, int backlog)
backlog:内核监听队列的最大长度,典型参数值是5.
d. int accept(int sockfd, struct sockaddr * addr, int addrlen)
addr:记录客户端的 IP 地址和端口号
addrlen:记录的客户端的 IP 地址和端口号的长度.
注意:bind和accept的addr不是同一个。
e. int connect(int sockfd, struct sockaddr* addr, int addrlen);
addr: 要连接的服务器的 IP 地址和端口号.
addrlen:要连接的服务器的 IP 地址和端口号的长度.
f. revc/send
revc(int sockfd, void *buf, int len, int flags);//读数据
send(int sockfd, const void *buf, int len, int flags);//写数据
buf:指定读/写缓冲区位置
len:指定读/写缓冲区大小
flags:置为0即可
接下来就是代码了
ser.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
void main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
struct sockaddr_in ser,cli;
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int res =bind(sockfd,(struct sockaddr *)&ser,sizeof(ser));
assert(res!=-1);
listen(sockfd,5);
while(1)
{
int len=sizeof(cli);
int c=accept(sockfd,(struct sockaddr*)&cli,&len);
assert(c!=-1);
char buff[128]={0};
recv(c,buff,127,0);
printf("recv::%s\n",buff);
send(c,"I know",strlen("I know"),0);
close(c);
}
close(sockfd);
}
cli.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
void main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
struct sockaddr_in ser,cli;
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);
send(sockfd,"hello world",strlen("hello world"),0);
char buff[128]={0};
recv(sockfd,buff,127,0);
printf("recv::%s\n",buff);
close(sockfd);
}
运行结果
注意:应该先运行服务器端,再运行客户端,当服务器在运行的时候,它会等待客户端给他发送请求,然后响应客服端的请求。要是先运行客户端,再运行服务器端,此时的服务器端是接收不到客户端的请求的,有兴趣的可以试一下,我自己在这上面踩得坑不少。