概念:端口号,实质就是一个数字编号,用来在我们一台主机中(主机的操作系统中)唯一的标识一个能上网的进程。端口号和IP地址一起会被打包到当前进程发出或者接收到的每一个数据包中。每一个数据包将来在网络上传递的时候,内部都包含了发送方和接收方的信息(就是IP地址和端口号),所以IP地址和端口号这两个往往是打包在一起不分家的。
IP 地址用来精确到 那一台 电脑
端口号:用来 精确 到这一台 电脑 某一个进程
3.9.6.1、服务器端程序编写
(1)socket : 建立一个网络连接
(2)bind : 把我当前的 IP 地址 和 端口号 和 socket 绑定起来
(3)listen : 监听
(4)accept,返回值是一个fd,accept正确返回就表示我们已经和前来连接我的客户端之间建立了一个TCP连接了,以后我们就要通过这个连接来和客户端进行读写操作,读写操作就需要一个fd,这个fd就由accept来返回了。
注意:socket返回的fd叫做监听fd,是用来监听客户端的,不能用来和任何客户端进行读写;accept返回的fd叫做连接fd,用来和连接那端的客户端程序进行读写。
3.9.9.socket编程实践4
3.9.9.1、自定义应用层协议第一步:规定发送和接收方法
(1)规定连接建立后由客户端主动向服务器发出1个请求数据包,然后服务器收到数据包后回复客户端一个回应数据包,这就是一个通信回合
(2)整个连接的通信就是由N多个回合组成的。
程序 有两个 .c 文件 服务器程序server.c , 和 客户端 程序 client.c
服务器程序server.c
#include
#include
#include
#include
#include
#include
#include
#include
#define SERPORT 6003
#define SERADDR "192.168.80.128 " /* 我的电脑做服务器 ubuntu 下 ifconfig 得到的 IP地址 */
#define BACKLOG 10
char recvbuf[100]={0};
int main(void)
{
int sockfd = -1; /* 定义一个接收 socket 文件描述符(socket 的返回值) */
int ret = -1;/* 定义一个接收 bind (bind 的返回值) */
int clifd = -1; /* 定义一个接收 accept (accept的返回值) */
struct sockaddr_in seraddr = {0}; /* 定义一个bind 第二个参数(输入型参数) 这个结构体是网络编程接口中用来表示一个IP地址的 */
struct sockaddr_in cliaddr = {0}; /* 定义一个accept 第二个参数(输出型参数) 这个结构体是网络编程接口中用来表示一个IP地址的 */
socklen_t len = 0;
/* 1 . 先 socket 打开文件描述符 */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
printf("sockfd = %d \n", sockfd);
/* 2 . bind 绑定sockfd 和当前电脑的 IP地址 和端口号 */
seraddr.sin_family = AF_INET; /* 设置 地址族为 IPV4 */
seraddr.sin_port = htons(SERPORT); /* 服务器端口号 htons 当前电脑 字节序 转成 网络 字节序 16位 */
seraddr.sin_addr.s_addr = inet_addr(SERADDR); /* 设置 IP地址 */
ret = bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
if(ret < 0)
{
perror("bind");
return -1;
}
printf("bind success \n");
/* 3 . listen ,监听 端口号 */
ret = listen(sockfd, BACKLOG);
if(ret < 0)
{
perror("listen");
return -1;
}
printf("服务器开始监听 \n");
/* 4 . accept 阻塞等待 客服端来连接服务器 */
clifd = accept(sockfd,(struct sockaddr *)&cliaddr, &len);/* 阻塞等待 客服端来连接服务器 */
printf("客户端 已连接服务器 clifd = %d \n",clifd); /*accept返回的fd叫做连接fd,用来和连接那端的客户端程序进行 读写。 */
/* 5 . 建立连接后 就可以通讯了 */
#if 0
ret = recv(clifd, recvbuf,sizeof(recvbuf),0); /* recv 接收出错会返回 -1 */
printf("成功接收了 %d 个字节\n",ret);
printf("client发送的内容是 %s \n",recvbuf);
#endif
while(1)
{
/* 1.回合第一步, 服务器接收 */
ret = recv(clifd, recvbuf,sizeof(recvbuf),0); /* recv 接收出错会返回 -1 */
printf("client发送的内容是 %s \n",recvbuf);
memset(recvbuf,0,sizeof(recvbuf));
/* 2.服务器机箱客户 数据包 */
/* 3.回合第三步, 回复客户端 */
ret = send(clifd, "ok",2,0); /* recv 接收出错会返回 -1 */
}
return 0;
}
/*************************************************************
3.9.4.5、表示IP地址相关数据结构
(1)都定义在 netinet/in.h
(2)struct sockaddr,这个结构体是网络编程接口中用来表示一个IP地址的,注意这个IP地址是不区分IPv4和IPv6的(或者说是兼容IPv4和IPv6的)
(3)typedef uint32_t in_addr_t; 网络内部用来表示IP地址的类型
(4)struct in_addr
{
in_addr_t s_addr;
};
(5)struct sockaddr_in
{
__SOCKADDR_COMMON (sin_); 宏定义 :这个是看 是IPV4 还是 IPV6
in_port_t sin_port; 端口号
struct in_addr sin_addr; IP地址
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
(6)struct sockaddr 这个结构体是linux的网络编程接口中用来表示IP地址的标准结构体,bind、connect等函数中都需要这个结构体,这个结构体是兼容IPV4和IPV6的。在实际编程中这个结构体会被一个struct sockaddr_in或者一个struct sockaddr_in6所填充。
const struct sockaddr *address 就等同于 struct sockaddr_in
*******************************************************************/
客户端 程序 client.c
#include
#include
#include
#include
#include
#include
#include
#include
/* 客户端端口 号 可以自己分配的,只有 和 服务器的 端口号 一直才能连接上 服务器 */
#define SERADDR "192.168.80.128 " /* 服务器给我们 开放的 IP 地址 和 端口号 */
#define SERPORT 6003
#define BACKLOG 10
char sendbuf[100]={0};
char recvbuf[100];
int main(void)
{
int sockfd = -1; /* 定义一个接收 socket 文件描述符(socket 的返回值) */
int ret = -1;/* 定义一个接收 bind (bind 的返回值) */
struct sockaddr_in seraddr = {0}; /* 定义一个connect 第二个参数(输入型参数) 这个结构体是网络编程接口中用来表示一个IP地址的 */
/* 1 . 先 socket 打开文件描述符 */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
printf("sockfd = %d \n", sockfd);
/* 2. connect */
seraddr.sin_family = AF_INET; /* 设置 地址族为 IPV4 */
seraddr.sin_port = htons(SERPORT); /* 服务器端口号 htons 当前电脑 字节序 转成 网络 字节序 16位 */
seraddr.sin_addr.s_addr = inet_addr(SERADDR); /* 设置 IP地址 */
ret = connect(sockfd,(const struct sockaddr *)&seraddr, sizeof(seraddr));
if(ret < 0)
{
perror("connect");
return -1;
}
printf("成功建立连接 \n");
/* 建立连接后 就可以通讯了 */
#if 0
strcpy(sendbuf,"hello world");
ret = send(sockfd,sendbuf,strlen(sendbuf), 0);
printf("发送了 %d 个字符 \n",ret);
#endif
while(1)
{
/* 1. 客户端 首先给服务器 发送数据包 */
printf("请输入要发送的内容\n");
scanf("%s",sendbuf);
//printf("刚才输入的是 %s \n",sendbuf);
ret = send(sockfd,sendbuf,strlen(sendbuf), 0);
printf("发送了多少个字节 %d \n",ret);
/* 2.回合第2步,客户端 接收 服务器的回复 */
memset(recvbuf,0,sizeof(recvbuf));
ret = recv(sockfd, recvbuf,sizeof(recvbuf),0); /* recv 接收出错会返回 -1 */
printf("接收到服务器 发送的内容是 %s \n",recvbuf);
/* 3.回合第3步,客户端 解析 服务器的回复,在做下一步定夺 */
}
return 0;
}
make
all:
gcc server.c -o ser
gcc client.c -o cli
clean:
rm ser cli *.o -rf
运行结果:
3.9.9.2、自定义应用层协议第二步:定义数据包格式
服务器程序server.c
#include
#include
#include
#include
#include
#include
#include
#include
#define SERPORT 6003
#define SERADDR "192.168.80.128 " /* 我的电脑做服务器 ubuntu 下 ifconfig 得到的 IP地址 */
#define BACKLOG 10
char recvbuf[100]={0};
#define CMD_REGISTER 1001 /*注册学生信息 */
#define CMD_CHECK 1002 /*检查 学生信息 */
#define CMD_GETINFO 1003 /*获取 学生信息 */
#define STAT_OK 30
#define STAT_ERR 31
typedef struct commu
{
char name[20]; /* 学生姓名 */
int age; /* 学生年龄 */
int cmd; /*命令码*/
int stat; /*状态信息, */
}info;
int main(void)
{
int sockfd = -1; /* 定义一个接收 socket 文件描述符(socket 的返回值) */
int ret = -1;/* 定义一个接收 bind (bind 的返回值) */
int clifd = -1; /* 定义一个接收 accept (accept的返回值) */
struct sockaddr_in seraddr = {0}; /* 定义一个bind 第二个参数(输入型参数) 这个结构体是网络编程接口中用来表示一个IP地址的 */
struct sockaddr_in cliaddr = {0}; /* 定义一个accept 第二个参数(输出型参数) 这个结构体是网络编程接口中用来表示一个IP地址的 */
socklen_t len = 0;
/* 1 . 先 socket 打开文件描述符 */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
printf("sockfd = %d \n", sockfd);
/* 2 . bind 绑定sockfd 和当前电脑的 IP地址 和端口号 */
seraddr.sin_family = AF_INET; /* 设置 地址族为 IPV4 */
seraddr.sin_port = htons(SERPORT); /* 服务器端口号 htons 当前电脑 字节序 转成 网络 字节序 16位 */
seraddr.sin_addr.s_addr = inet_addr(SERADDR); /* 设置 IP地址 */
ret = bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
if(ret < 0)
{
perror("bind");
return -1;
}
printf("bind success \n");
/* 3 . listen ,监听 端口号 */
ret = listen(sockfd, BACKLOG);
if(ret < 0)
{
perror("listen");
return -1;
}
printf("服务器开始监听 \n");
/* 4 . accept 阻塞等待 客服端来连接服务器 */
clifd = accept(sockfd,(struct sockaddr *)&cliaddr, &len);/* 阻塞等待 客服端来连接服务器 */
printf("客户端 已连接服务器 clifd = %d \n",clifd); /*accept返回的fd叫做连接fd,用来和连接那端的客户端程序进行 读写。 */
/* 5 . 建立连接后 就可以通讯了 */
#if 0
ret = recv(clifd, recvbuf,sizeof(recvbuf),0); /* recv 接收出错会返回 -1 */
printf("成功接收了 %d 个字节\n",ret);
printf("client发送的内容是 %s \n",recvbuf);
#endif
while(1)
{
/* 1.回合第一步, 服务器接收 */
info st;
ret = recv(clifd, &st,sizeof(info),0); /* recv 接收出错会返回 -1 */
/* 2.服务器解析 客户 数据包 */
if(st.cmd == CMD_REGISTER)
{
printf("用户要注册学生信息 \n");
printf("学生姓名:%s, 学生年龄: %d \n",st.name, st.age);
/* 3.回合第三步, 回复客户端 */
st.stat = STAT_OK;
ret = send(clifd, &st,sizeof(info),0); /* recv 接收出错会返回 -1 */
}
if(st.cmd == CMD_CHECK)
{
}
}
return 0;
}
/*************************************************************
3.9.4.5、表示IP地址相关数据结构
(1)都定义在 netinet/in.h
(2)struct sockaddr,这个结构体是网络编程接口中用来表示一个IP地址的,注意这个IP地址是不区分IPv4和IPv6的(或者说是兼容IPv4和IPv6的)
(3)typedef uint32_t in_addr_t; 网络内部用来表示IP地址的类型
(4)struct in_addr
{
in_addr_t s_addr;
};
(5)struct sockaddr_in
{
__SOCKADDR_COMMON (sin_); 宏定义 :这个是看 是IPV4 还是 IPV6
in_port_t sin_port; 端口号
struct in_addr sin_addr; IP地址
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
(6)struct sockaddr 这个结构体是linux的网络编程接口中用来表示IP地址的标准结构体,bind、connect等函数中都需要这个结构体,这个结构体是兼容IPV4和IPV6的。在实际编程中这个结构体会被一个struct sockaddr_in或者一个struct sockaddr_in6所填充。
const struct sockaddr *address 就等同于 struct sockaddr_in
*******************************************************************/
客户端程序 client.c
#include
#include
#include
#include
#include
#include
#include
#include
/* 客户端端口 号 可以自己分配的,只有 和 服务器的 端口号 一直才能连接上 服务器 */
#define SERADDR "192.168.80.128 " /* 服务器给我们 开放的 IP 地址 和 端口号 */
#define SERPORT 6003
#define BACKLOG 10
char sendbuf[100]={0};
char recvbuf[100];
#define CMD_REGISTER 1001 /*注册学生信息 */
#define CMD_CHECK 1002 /*检查 学生信息 */
#define CMD_GETINFO 1003 /*获取 学生信息 */
#define STAT_OK 30
#define STAT_ERR 31
typedef struct commu
{
char name[20]; /* 学生姓名 */
int age; /* 学生年龄 */
int cmd; /*命令码*/
int stat; /*状态信息, */
}info;
int main(void)
{
int sockfd = -1; /* 定义一个接收 socket 文件描述符(socket 的返回值) */
int ret = -1;/* 定义一个接收 bind (bind 的返回值) */
struct sockaddr_in seraddr = {0}; /* 定义一个connect 第二个参数(输入型参数) 这个结构体是网络编程接口中用来表示一个IP地址的 */
/* 1 . 先 socket 打开文件描述符 */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
printf("sockfd = %d \n", sockfd);
/* 2. connect */
seraddr.sin_family = AF_INET; /* 设置 地址族为 IPV4 */
seraddr.sin_port = htons(SERPORT); /* 服务器端口号 htons 当前电脑 字节序 转成 网络 字节序 16位 */
seraddr.sin_addr.s_addr = inet_addr(SERADDR); /* 设置 IP地址 */
ret = connect(sockfd,(const struct sockaddr *)&seraddr, sizeof(seraddr));
if(ret < 0)
{
perror("connect");
return -1;
}
printf("成功建立连接 \n");
/* 建立连接后 就可以通讯了 */
#if 0
strcpy(sendbuf,"hello world");
ret = send(sockfd,sendbuf,strlen(sendbuf), 0);
printf("发送了 %d 个字符 \n",ret);
#endif
while(1)
{
/* 1. 客户端 首先给服务器 发送数据包 */
info st1;
printf("请输入学生姓名\n");
scanf("%s",st1.name);
printf("请输入学生年龄\n");
scanf("%d",&st1.age);
st1.cmd = CMD_REGISTER;
//printf("刚才输入的是 %s \n",sendbuf);
ret = send(sockfd,&st1,sizeof(info), 0);
printf("发送了多少个字节 %d \n",ret);
/* 2.回合第2步,客户端 接收 服务器的回复 */
memset(&st1,0,sizeof(info));
ret = recv(sockfd, &st1,sizeof(info),0); /* recv 接收出错会返回 -1 */
/* 3.回合第3步,客户端 解析 服务器的回复,在做下一步定夺 */
if(st1.stat == STAT_OK)
{
printf("注册学生信息成功 \n");
printf(" \n");
}
else
{
printf("注册学生信息失败 \n");
}
}
return 0;
}
运行结果:
3.9.9.3、常用应用层协议:http、ftp······
3.9.9.4、UDP简介