TCP编程
- 流程
- 服务器
- 客户端
- 函数接口
- 1、socket
- 2、bind
- 3、listen
- 4、accept
- 5、recv
- 6、send
- 7、connet
- 实现双工通信
- server.c
- celient.c
- 优化代码
流程
在C语言中进行TCP编程的一般步骤如下:
(1)包含头文件:
在代码中包含必要的头文件,以便使用TCP编程所需的函数和数据类型。通常情况下,你需要包含 <sys/socket.h>、<netinet/in.h> 和 <arpa/inet.h>。
(2)创建套接字:
使用 socket() 函数创建一个套接字,该套接字将用于网络通信。套接字是一个整数值,它表示一个打开的文件描述符,用于在网络上发送和接收数据。
(3)设置地址和端口:
创建一个 struct sockaddr_in 结构体,并设置其中的成员变量,包括地址和端口号。这个结构体用于指定服务器的地址和端口。
(4)绑定套接字:
使用 bind() 函数将套接字绑定到指定的地址和端口上。这将使服务器能够监听指定的端口并接受客户端的连接。
(5)监听连接:
使用 listen() 函数开始监听连接请求。这将使服务器进入被动等待状态,等待客户端连接。
(6)接受连接:
使用 accept() 函数接受客户端的连接请求。当有客户端连接到服务器时,accept() 函数将返回一个新的套接字,该套接字用于与客户端进行通信。
(7)通信:
使用 send() 和 recv() 函数发送和接收数据。服务器和客户端都可以使用这些函数来发送和接收数据。
(8)关闭连接:
在通信结束后,使用 close() 函数关闭套接字,释放资源。
这些步骤提供了一个基本的框架来进行TCP编程。你可以根据需要进行适当的修改和扩展。同时还需要处理错误和异常情况,并确保适当地释放资源,以避免内存泄漏和其他问题。
服务器
(1)socket:创建一个用与链接的套接字(用于链接)
(2)bind:绑定自己的ip地址和端口
(3)listen:监听,将主动套接字转为被动套接字
(4)accept:阻塞等待客户端链接,链接成功返回一个用于通信套接字
(5)recv:接收消息
(6)send:发送消息
(7)close:关闭文件描述符
客户端
(1)socket:创建一个套接字
(2)填充结构体:填充服务器的ip和端口
(3)connect:阻塞等待链接服务器
(4)recv/send:接收/发送消息
(5)close:关闭
函数接口
1、socket
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain,int type,int protocol);
功能:创建套接字文件
参数:
domain:协议族 ,选择通信方式
AF_UNIX, AF_LOCAL 本地通信
AF_INET IPv4
AF_INET6 IPv6
type:通信协议-套接字类型
SOCK_STREAM 流式套接字
SOCK_DGRAM 数据报套接字
SOCK_RAW 原始套接字
protocol:协议 填0,自动匹配底层TCP或UDP等协议。根据type匹配系统默认自动帮助匹配对应协议
传输层:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP
网络层:htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)
返回值:成功。返回同于链接的文件描述符
失败 -1,更新errno
2、bind
```c
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
功能:绑定套接字 - ip和端口
功能:
sockfd:套接字文件描述符
addr:用于通信结构体 (提供的是通用结构体,需要根据选择通信方式,填充对应结构体-通信结构体由socket第一个参数确定)
addrlen:结构体大小
返回值: 成功0
失败:-1 更新errno
通用结构体:
struct sockaddr{
sa_family_t sa_family;
char sa_data[14];
}
ipv4的通信结构体:
struct sockaddr_in{
sa_family_t sin_family;/*AF_INET */
in_port_t sin_port;/* 端口 */
struct in_addr sin_addr;/* ip地址 */
};
struct in_addr{
uint32_t s_addr;
};
本地通信结构体:
struct sockaddr_un{
sa_family_t sun_family;/* AF_UNIX */
char sun_path[108];/* 套接字文件 */
};
3、listen
int listen(int sockfd,int backlog);
功能:监听,将主动套接字变为被动套接字
参数:
sockfd:套接字
backlog:同时响应客户端请求链接的最大个数,不能写0.
不同平台可同时链接的数不同,一般写6-8个
(队列1:保存正在连接)
(队列2,连接上的客户端)
返回值:成功 0 失败-1,更新errno
4、accept
int accept(int sockfd,struct sockaddr* addr,socklen_t* addrlen);
accept(sockfd,NULL,NULL);
阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,
则accept()函数返回,返回一个用于通信的套接字文件;
参数:
Sockfd :套接字
addr: 链接客户端的ip和端口号
如果不需要关心具体是哪一个客户端,那么可以填NULL;
addrlen:结构体的大小
如果不需要关心具体是哪一个客户端,那么可以填NULL;
返回值:
成功:文件描述符;//用于通信
失败:-1,更新errno
//监听套接字,将主动套接字转为被动套接字
if (listen(sockfd, 5) < 0)
{
perror("listern error.");
return -1;
}
//4.阻塞等待客户端链接,链接成功返回一个用于通信的文件描述符
acceptfd = accept(sockfd, NULL, NULL);
if (acceptfd < 0)
{
perror("accept error.");
return -1;
}
printf("acceptfd=%d\n", acceptfd);
5、recv
ssize_t recv(int sockfd,void*buf, size_t len,int flags);
功能: 接收数据
参数:
sockfd: acceptfd ;
buf 存放位置
len 大小
flags 一般填0,相当于read()函数
MSG_DONTWAIT 非阻塞
返回值:
<0 失败出错 更新errno
==0 表示客户端退出
>0 成功接收的字节个数
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//创建套接字
int sockfd;
int acceptfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket error.");
return -1;
}
printf("sockfd=%d\n", sockfd);
//填充ipv4的通信结构体
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(8888);
serveraddr.sin_addr.s_addr = inet_addr("192.168.50.83");
//绑定套接字
if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
perror("bind error.");
return -1;
}
printf("bind ok.\n");
//3.监听套接字,将主动套接字转为被动套接字
if (listen(sockfd, 5) < 0)
{
perror("listern error.");
return -1;
}
//4.阻塞等待客户端链接,链接成功返回一个用于通信的文件描述符
acceptfd = accept(sockfd, NULL, NULL);
if (acceptfd < 0)
{
perror("accept error.");
return -1;
}
printf("acceptfd=%d\n", acceptfd);
//5.循环接收消息
char buf[128];
int recvbyte;
while (1)
{
recvbyte = recv(acceptfd, buf, sizeof(buf), 0);
if (recvbyte < 0)
{
perror("recv error.");
return -1;
}
else if (recvbyte == 0)
{
printf("client exit.\n");
break;
}
else
{
buf[recvbyte] = '\0';
printf("buf:%s\n", buf);
}
}
close(acceptfd);
close(sockfd);
return 0;
}
6、send
ssize_t send(int sockfd,constvoid*buf, size_t len,int flags);
功能:发送数据
参数:
sockfd:socket函数的返回值
buf:发送内容存放的地址
len:发送内存的长度
flags:如果填0,相当于write();
返回值:
<0 失败出错 更新errno
>0 成功发送的字节个数
7、connet
int connect(int sockfd,const struct sockaddr*addr,socklen_t addrlen);
功能:用于连接服务器;
参数:
sockfd:socket函数的返回值
addr:填充的结构体是服务器端的;
addrlen:结构体的大小
返回值
-1 失败,更新errno
正确 0
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//创建套接字
int sockfd;
int acceptfd;
char buf[128];
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket error.");
return -1;
}
printf("sockfd=%d\n", sockfd);
//填充ipv4的通信结构体
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(8888);
serveraddr.sin_addr.s_addr = inet_addr("192.168.50.83");
//连接到服务器端
if (connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
perror("connect err.");
return -1;
}
printf("connect success.\n");
ssize_t printbyte;
while (1)
{
memset(buf, 0, sizeof(buf));
printf("send:");
scanf("%s", buf);
if ((printbyte = send(sockfd, buf, sizeof(buf), 0)) <0)
{
perror("send err.");
}
}
return 0;
}
实现双工通信
server.c
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
int main(int argc, char const *argv[])
{
if (argc != 2)
{
printf("please input %s <port>\n", argv[0]);
return -1;
}
int sockfd, acceptfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err.");
return -1;
}
struct sockaddr_in serveraddr, caddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[1]));
// serveraddr.sin_addr.s_addr=inet_addr(argv[1]);
//自动获取ip
//serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
// serveraddr.sin_addr.s_addr=INADDR_ANY; //0.0.0.0
serveraddr.sin_addr.s_addr = inet_addr("0.0.0.0");
socklen_t len = sizeof(caddr);
if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
perror("bind err.");
return -1;
}
if (listen(sockfd, 5) < 0)
{
perror("listen err.");
return -1;
}
while (1)
{
acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
if (acceptfd < 0)
{
perror("accept err.");
return -1;
}
printf("client:ip=%s port=%d\n",
inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
char buf[128];
pid_t pid = fork();
if (pid < 0)
{
perror("fork err.");
return -1;
}
else if (pid == 0) //发送
{
int sendbyte;
while (1)
{
fgets(buf, sizeof(buf), stdin);
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
sendbyte = send(acceptfd, buf, sizeof(buf), 0);
if (sendbyte < 0)
{
perror("send error.");
return -1;
}
}
}
else
{
int recvbyte;
while (1)
{
recvbyte = recv(acceptfd, buf, sizeof(buf), 0);
if (recvbyte < 0)
{
perror("recv err.");
return -1;
}
else if (recvbyte == 0)
{
printf("client exit.\n");
kill(pid,SIGKILL);
wait(NULL);
break;
}
else
{
printf("buf:%s\n", buf);
}
}
close(acceptfd);
}
}
close(sockfd);
return 0;
}
celient.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
if (argc != 3)
{
printf("please input %s <ip> <port>\n", argv[0]);
return -1;
}
//1.创建套接子
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket error.");
return -1;
}
printf("sockfd=%d\n", sockfd);
//填充ipv4的通信结构体 服务器的ip和端口
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
//2.请求链接服务器
if (connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
perror("connect error.");
return -1;
}
//5.循环发送消息 通信
char buf[128];
pid_t pid = fork();
if (pid < 0)
{
perror("fork err.");
return -1;
}
else if (pid == 0)
{
int recvbyte;
while (1)
{
recvbyte = recv(sockfd, buf, sizeof(buf), 0);
if (recvbyte < 0)
{
perror("recv err.");
return -1;
}
printf("buf:%s\n", buf);
}
}
else
{
int sendbyte;
while (1)
{
fgets(buf, sizeof(buf), stdin);
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
sendbyte = send(sockfd, buf, sizeof(buf), 0);
if (sendbyte < 0)
{
perror("send error.");
return -1;
}
if(strncmp(buf,"quit",4)==0)
{
kill(pid,SIGKILL);
wait(NULL);
exit(-1);
}
}
}
close(sockfd);
return 0;
}
优化代码
1.去掉fget获取多余的‘\n’
if(buf[strlen(buf)-1]=='\n')
buf[strlen(buf)-1]='\0';
2.端口和ip地址通过命令行传参到代码中。
3.设置客户端退出,服务器结束循环接收。
通过recv返回值为0判断客户端是否退出。
4.设置来电显示功能,获取到请求链接服务器的客户端的ip和端口。
5.设置服务器端自动获取自己的ip地址。
INADDR_ANY "0.0.0.0"
6.实现循环服务器,服务器不退出,当链接服务器的客户端退出,服务器等到下一个客户端链接。