目录
[1 函数介绍](#1 函数介绍)
[1.1 socket函数 与 通信域](#1.1 socket函数 与 通信域)
[1.2 bind函数 与 通信结构体](#1.2 bind函数 与 通信结构体)
[1.3 listen函数 与 accept函数](#1.3 listen函数 与 accept函数)
[2 TCP服务端代码实现](#2 TCP服务端代码实现)
[3 TCP客户端代码实现](#3 TCP客户端代码实现)
[4 代码优化](#4 代码优化)
[5 练习](#5 练习)
1 函数介绍
其中read、write、close在IO中已经介绍过,只需了解socket、bind、listen、accept等
1.1 socket函数 与 通信域
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数:
domain
:指定套接字的协议域(protocol family),可以是AF_INET
(IPv4)或AF_INET6
(IPv6)等。type
:指定套接字的类型,可以是SOCK_STREAM
(流套接字,用于可靠的、面向连接的通信)或SOCK_DGRAM
(数据报套接字,用于无连接的通信)等。protocol
:指定使用的协议,可以是IPPROTO_TCP
(TCP)或IPPROTO_UDP
(UDP)等。所以无需要指定协议,设为0即可
返回值:
- 成功创建套接字时,返回一个非负整数,代表新创建的套接字描述符。
- 创建套接字失败时,返回
-1
,并设置errno
来表示具体的错误原因。
示例:
cpp
#include <sys/types.h>
#include <sys/socket.h>
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //最后一个参数也可以是0
if (sockfd == -1) {
// 处理创建套接字失败的情况
return -1;
}
// 套接字创建成功,可以进行后续操作
return 0;
}
1.2 bind函数 与 通信结构体
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数解释:
sockfd
:要进行绑定的套接字描述符。addr
:指向sockaddr
结构体的指针,其中包含了要绑定的地址信息。addrlen
:addr
指向的结构体的大小。
返回值:
- 成功时,返回0。
- 失败时,返回-1,并且在错误码中设置相应的错误标志,可以通过
errno
全局变量获取具体错误信息。
ipv4结构体
struct sockaddr_in {
sa_family_t sin_family; /* 地址族: AF_INET */
in_port_t sin_port; /* 网络字节序的端口号 */
struct in_addr sin_addr; /*IP地址结构体 */
};
/* IP地址结构体 */
struct in_addr {
uint32_t s_addr; /* 网络字节序的IP地址 */
};
/*通用地址族结构体*/
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
注意事项:
- 调用
bind()
函数之前,需要先创建一个套接字,并确保该套接字是未绑定的。 bind()
函数通常在服务器端使用,用于将服务器的套接字与指定的本地地址绑定,从而监听并接收该地址发来的连接请求。- 在调用
bind()
函数时,要根据实际情况提供正确的地址信息,如IP地址和端口号等。 - 在IPv4中,地址信息存储在
sockaddr_in
结构体中;而在IPv6中,地址信息存储在sockaddr_in6
结构体中。
示例:强制转换
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
int sockfd;
struct sockaddr_in server_addr;
// 创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 设置服务器地址信息
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
server_addr.sin_addr.s_addr = INADDR_ANY;
// 绑定套接字和地址
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
return 1;
}
// 其他操作...
return 0;
}
1.3 listen函数 与 accept函数
/*监听套接字*/
int listen(int sockfd, int backlog);
参数:
sockfd
:要监听的套接字描述符。backlog
:定义允许排队等待的连接请求的最大数量。
返回值:
- 成功调用
listen()
函数时,返回0
表示成功。 - 调用
listen()
函数失败时,返回-1
并设置errno
来表示具体的错误原因。
函数功能: listen()
函数被用于 TCP 服务器端,用于将指定的套接字标记为被动套接字(passive socket),开始监听传入的连接请求。在调用 listen()
之前,服务器需要使用 socket()
函数创建一个套接字,并使用 bind()
函数将套接字与特定的地址和端口绑定。
一旦套接字被标记为监听状态,它就可以开始接受传入的连接请求。这些连接请求会被放置在一个连接请求队列中,等待服务器进程使用 accept()
函数来接受这些请求并建立连接。
注意事项:
backlog
参数指定了连接请求队列的最大长度。如果队列已满,则新的连接请求将被拒绝。实际允许的队列长度可能会受到系统限制。- 在调用
listen()
之后,通常需要调用accept()
函数来接受连接请求并建立连接。
示例:
#include <sys/types.h>
#include <sys/socket.h>
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockfd == -1) {
// 处理创建套接字失败的情况
return -1;
}
// 套接字创建成功,可以进行后续操作
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
// 处理绑定地址和端口失败的情况
return -1;
}
if (listen(sockfd, 10) == -1) {
// 处理监听套接字失败的情况
return -1;
}
// 套接字处于监听状态,可以接受连接请求并建立连接
return 0;
}
/*处理客户端发起的连接,生成新的套接字*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
-sockfd: 函数socket生成的套接字
-addr:客户端的地址族信息
-addrlen:地址族结构体的长度
参数:
sockfd
:监听套接字描述符,即之前调用listen()
函数返回的套接字描述符。addr
:指向用于存储客户端地址信息的结构体sockaddr
的指针,可以为NULL
。addrlen
:指向存储客户端地址长度的变量的指针,如果addr
不为NULL
,则需要将addrlen
设置为sizeof(struct sockaddr)
。
返回值:
- 成功调用
accept()
函数时,返回一个新的套接字描述符,用于处理与客户端的连接。 - 调用
accept()
函数失败时,返回-1
并设置errno
来表示具体的错误原因。
函数功能: accept()
函数用于监听套接字上接受传入的连接请求,并创建一个新的套接字来处理与客户端的连接。该新的套接字用于与客户端进行通信。在调用 accept()
函数之前,需要先使用 socket()
、bind()
和 listen()
函数来准备监听套接字。
当有一个连接请求到达监听套接字时,accept()
函数会从连接请求队列中取出一个请求,创建一个新的套接字来处理该连接,并返回新创建的套接字描述符。可以通过新创建的套接字描述符进行与客户端的通信。
如果传入的 addr
不为 NULL
,则 accept()
函数会将客户端的地址信息存储在 addr
指向的结构体中。同时,addrlen
也需要传入一个指向存储客户端地址长度的变量的指针。
注意事项:
accept()
函数是一个阻塞调用,当没有连接请求时,它会一直等待,直到有连接请求到达或出现错误才返回。- 通常在多线程或多进程环境中使用
accept()
函数来实现并发处理多个连接请求的功能。
示例:
#include <sys/types.h>
#include <sys/socket.h>
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockfd == -1) {
// 处理创建套接字失败的情况
return -1;
}
// 套接字创建成功,可以进行后续操作
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
// 处理绑定地址和端口失败的情况
return -1;
}
if (listen(sockfd, 10) == -1) {
// 处理监听套接字失败的情况
return -1;
}
// 套接字处于监听状态,可以接受连接请求并建立连接
struct sockaddr_in client_addr;
socklen_t client_addrlen = sizeof(client_addr);
int client_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_addrlen);
if (client_sockfd == -1) {
// 处理接受连接请求失败的情况
return -1;
}
// 成功接受连接请求,可以使用 client_sockfd 进行与客户端的通信
return 0;
}
2 TCP服务端代码实现
cpp
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 5001
#define BACKLOG 5
int main(int argc, char *argv[])
{
int fd, newfd;
char buf[BUFSIZ] = {}; //BUFSIZ 8142
struct sockaddr_in addr;
/*创建套接字*/
fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0){
perror("socket");
exit(0);
}
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = 0;
/*绑定通信结构体*/
if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
perror("bind");
exit(0);
}
/*设置套接字为监听模式*/
if(listen(fd, BACKLOG) == -1){
perror("listen");
exit(0);
}
/*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/
newfd = accept(fd, NULL, NULL);
if(newfd < 0){
perror("accept");
exit(0);
}
printf("BUFSIZ = %d\n", BUFSIZ);
read(newfd, buf, BUFSIZ);
printf("buf = %s\n", buf);
close(fd);
return 0;
}
3 TCP客户端代码实现
cpp
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 5001
#define BACKLOG 5
#define STR "Hello World!"
int main(int argc, char *argv[])
{
int fd;
struct sockaddr_in addr;
/*创建套接字*/
fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0){
perror("socket");
exit(0);
}
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
/*向服务端发起连接请求*/
if(connect(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
perror("connect");
exit(0);
}
write(fd, STR, sizeof(STR) );
printf("STR = %s\n", STR);
close(fd);
return 0;
}
4 代码优化
服务端
cpp
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#define BACKLOG 5
int main(int argc, char *argv[])
{
int fd, newfd, ret;
char buf[BUFSIZ] = {}; //BUFSIZ 8142
struct sockaddr_in addr;
if(argc < 3){
fprintf(stderr, "%s<addr><port>\n", argv[0]);
exit(0);
}
/*创建套接字*/
fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0){
perror("socket");
exit(0);
}
addr.sin_family = AF_INET;
addr.sin_port = htons( atoi(argv[2]) );
if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
/*绑定通信结构体*/
if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
perror("bind");
exit(0);
}
/*设置套接字为监听模式*/
if(listen(fd, BACKLOG) == -1){
perror("listen");
exit(0);
}
/*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/
newfd = accept(fd, NULL, NULL);
if(newfd < 0){
perror("accept");
exit(0);
}
while(1){
memset(buf, 0, BUFSIZ);
ret = read(newfd, buf, BUFSIZ);
if(ret < 0)
{
perror("read");
exit(0);
}
else if(ret == 0)
break;
else
printf("buf = %s\n", buf);
}
close(newfd);
close(fd);
return 0;
}
客户端
cpp
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#define BACKLOG 5
int main(int argc, char *argv[])
{
int fd;
struct sockaddr_in addr;
char buf[BUFSIZ] = {};
if(argc < 3){
fprintf(stderr, "%s<addr><port>\n", argv[0]);
exit(0);
}
/*创建套接字*/
fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0){
perror("socket");
exit(0);
}
addr.sin_family = AF_INET;
addr.sin_port = htons( atoi(argv[2]) );
if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
/*向服务端发起连接请求*/
if(connect(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
perror("connect");
exit(0);
}
while(1){
printf("Input->");
fgets(buf, BUFSIZ, stdin);
write(fd, buf, strlen(buf) );
}
close(fd);
return 0;
}
5 练习
实现TCP通信代码,并使用Makefile进行编译。提交代码和完成通信的截图
client
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#define CLIENT_MAX_NUM 5
int main(int argc, char * argv[])
{
int clientfd;
struct sockaddr_in server_addr;
char buf[BUFSIZ];
int ret;
if( argc < 3)
{
printf("%s <ip> <port>\n",argv[0]);
return 0;
}
clientfd = socket(AF_INET, SOCK_STREAM,0);
if(clientfd == -1)
{
perror("socket");
return 0;
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons( atoi(argv[2]) ) ;
if( inet_aton(argv[1], &server_addr.sin_addr) == 0)
{
printf("Invalid address:%s\n",argv[1]);
return 0;
}
if(connect(clientfd, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
{
perror("connect");
return 0;
}
while(1)
{
printf(">");
fgets(buf, BUFSIZ, stdin);
write(clientfd, buf, strlen(buf));
}
close(clientfd);
return 0;
}
server
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#define CLIENT_MAX_NUM 5
int main(int argc, char * argv[])
{
int sockfd, clientfd;
struct sockaddr_in server_addr;
char buf[BUFSIZ];
int ret;
if( argc < 3)
{
printf("%s <ip> <port>\n",argv[0]);
return 0;
}
sockfd = socket(AF_INET, SOCK_STREAM,0);
if(sockfd == -1)
{
perror("socket");
return 0;
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons( atoi(argv[2]) ) ;
if( inet_aton(argv[1], &server_addr.sin_addr) == 0)
{
printf("Invalid address:%s\n",argv[1]);
return 0;
}
if(bind(sockfd, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
{
perror("bind");
return 0;
}
if(listen(sockfd, CLIENT_MAX_NUM) == -1)
{
perror("listen");
return 0;
}
clientfd = accept(sockfd, NULL, NULL);
if( clientfd == -1)
{
perror("accept");
return 0;
}
while(1)
{
memset(buf, 0, BUFSIZ);
ret = read(clientfd, buf, BUFSIZ);
if(ret < 0)
{
perror("read");
return 0;
}
else if( ret == 0 )
{
break;
}
else
{
printf("buf = %s\n", buf);
}
}
close(clientfd);
close(sockfd);
return 0;
}
makefile
cpp
CC=gcc
CFLAGS=-Wall
all:tcp_client tcp_server
clean:
rm tcp_server tcp_client