关于TCP:TCP提供的是一种面向连接的、可靠的字节流服务。
TCP网络通信流程图
补充的API
函数名称:listen()
原型:int listen(int sockfd, int backlog)
功能 : 宣告服务器能够接收的连接请求数
头文件:<sys/socket.h>
成功:0 失败:-1
参数说明:
sockfd : 套接字
backlog:请求队列允许的最大请求数,大多数系统默认为20
函数名称:accept()
原型:int accept(sockfd, struct sockaddr *restrict addr, socklen_t *restrict len)
功能 : 阻塞服务器,获得连接请求并建立连接
头文件:<sys/socket.h>
成功:返回新的关于该连接的套接字 失败:-1
参数说明:
sockfd : 套接字
addr:客户端地址信息
len:客户端地址长度
函数名称:send()
原型:int send(int sockfd, const void *buf , size_t n, int flags)
功能 : 发送数据(此时套接字必须已连接)
头文件:<sys/socket.h>
成功:返回发送的字节数 失败:-1
参数说明:
sockfd : 套接字
buf:待发送的数据
n:发送数据长度
flags:通常为0
函数名称:connect() --->可以看出通常是由客户端发送连接请求
原型:int connect(int sockfd, const struct sockaddr *addr, socklen_t len)
功能 : 在客户端与服务端建立连接
头文件:<sys/socket.h>
成功:0 失败:-1
参数说明:
sockfd : 套接字
addr:与之通信的服务器地址,如果sockfd没有绑定到一个地址,则,connect会给调用者绑定一个默认地址
len:地址长度
函数名称:recv()
原型:int recv(int sockfd, void *buf , size_t n, int flags)
功能 : 接收数据(此时套接字必须已连接)
头文件:<sys/socket.h>
成功:返回接收的字节数 无可用数据则返回0 失败:返回-1
参数说明:
sockfd : 套接字
buf:存放接收数据的缓冲区
n:缓冲区长度
flags:通常为0
服务器工作流程:
1. 创建、初始化服务器套接字,创建初始化服务器/客户端地址结构
2. 绑定服务器套接字(bind)
3. 监听sockfd描述符( listen )
4. 服务器阻塞,直到获得连接请求并建立连接(accept)
5. 接收客户端发送的数据,这里以读取客户端的IP作为示例
6. 发送数据到客户端,通过新的套接字。
代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int sockfd, port, new_fd;
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
char hello[] = "hello";
int size;
/*判断用法是否正确*/
if(argc != 2)
{
fprintf(stderr, "usage: %s port", strerror(errno));
exit(1);
}
/*获取端口号*/
if((port = atoi(argv[1])) < 0)
{
fprintf(stderr, "Port Error: %s", strerror(errno));
exit(1);
}
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
fprintf(stderr, "socket error: %s", strerror(errno));
exit(1);
}
/*填充sockaddr结构*/
bzero(&s_addr, sizeof(s_addr));
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(port);
s_addr.sin_addr.s_addr = htonl(INADDR_ANY);
/*绑定套接字与描述符*/
if(bind(sockfd, (struct sockaddr*)&s_addr, sizeof(s_addr)) == -1)
{
fprintf(stderr, "bind error: %s", strerror(errno));
exit(1);
}
printf("socket...\n");
/*监听sockfd描述符*/
if(listen(sockfd, 10) == -1)
{
fprintf(stderr, "listen error: %s", strerror(errno));
exit(1);
}
printf(" listen...\n");
while(1)
{
size = sizeof(struct sockaddr);
/*阻塞服务器,获取连接*/
printf(" accept...\n");
if((new_fd = accept(sockfd, (struct sockaddr*)&c_addr, &size)) < 0)
{
fprintf(stderr, "accept error: %s", strerror(errno));
exit(1);
}
fprintf(stderr, "new_fd = %d\n", new_fd);
/*向连接的新套接字字符发送数据*/
if((send(new_fd, hello, strlen(hello), 0)) < 0)
{
fprintf(stderr, "send error: %s", strerror(errno));
exit(1);
}
}
close(sockfd);
exit(0);
}
客户端工作流程:
1. 判断使用方法,提取端口号
2. 创建套接字(面向字节流),初始化套接字
3. 在请求服务的进程套接字(客户端)和提供服务的进程套接字(服务端)建立连接
4. 接收数据,打印
通信演示:
服务端:
客户端: