网络篇二--TCP编程
一、TCP编程实现
网络编程相关API
网络编程常用函数
socket() 创建套接字
bind() 绑定本机地址和端口
connect() 建立连接
listen() 设置监听端口
accept() 接受TCP连接
recv(), read(), recvfrom() 数据接收
send(), write(), sendto() 数据发送
close(), shutdown() 关闭套接字
1、编程步骤
2、socket函数
int socket (int domain, int type, int protocol);
domain
是地址族
AF_INET
// internet 协议AF_UNIX
// unix internal协议AF_NS
// Xerox NS协议AF_IMPLINK
// Interface Message协议
type
// 套接字类型SOCK_STREAM
// 流式套接字-TCP
SOCK_DGRAM
// 数据报套接字-UDP
SOCK_RAW
// 原始套接字-直接传输
protocol
参数通常置为0
3、bind函数
int bind (int sockfd, struct sockaddr* addr, int addrLen);
sockfd
由socket()
调用返回addr
是指向sockaddr 结构
的指针
,包含本机IP 地址
和端口号
通用地址结构
struct sockaddr
{
u_short sa_family; // 地址族, AF_xxx
char sa_data[14]; // 14字节协议地址
};
我们使用的是:Internet协议地址结构
struct sockaddr_in
{
u_short sin_family; // 地址族, AF_INET,2 bytes
u_short sin_port; // 端口,2 bytes
struct in_addr sin_addr; // IPV4地址,4 bytes
char sin_zero[8]; // 8 bytes unused,作为填充
};
// internet address
struct in_addr
{
in_addr_t s_addr; // u32 network address
};
addrLen
: 结构体大小sizeof (struct sockaddr_in)
一般用法:
(1)定义一个struct sockaddr_in类型的变量并清空
struct sockaddr_in myaddr;
memset(&myaddr, 0, sizeof(myaddr));
(2)填充地址信息
myaddr.sin_family = PF_INET;
myaddr.sin_port = htons(8888);
myaddr.sin_addr.s_addr = inet_addr(“192.168.1.100”);
(3)将该变量强制转换为struct sockaddr类型在函数中使用
bind(listenfd, (struct sockaddr*)(&myaddr), sizeof(myaddr));
4、地址转换函数
(1)主机转网络字序
unsigned long inet_addr(char *address);
address
是以’\0’
结尾的点分IPv4字符串
。该函数返回32位
的地址数据。
如果字符串包含的不是合法的IP地址,则函数返回-1
。
例如:
struct in_addr addr;
addr.s_addr = inet_addr(" 192.168.1.100 ");
(2)网络转主机字序
char* inet_ntoa(struct in_addr address);
address
是IPv4地址结构
,函数返回一指向包含点分IP地址的静态存储区字符指针
。如果错误
则函数返回NULL
5、listen函数
int listen (int sockfd, int backlog);
sockfd
: 监听连接的套接字backlog
:
指定了正在等待连接
的最大队列长度
,它的作用在于处理可能同时出现的几个连接请求。
返回值: 0 或 -1
完成listen()调用后,socket变成了监听socket(listening socket).
6、accept函数
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr,
socklen_t *addrlen) ;
返回值:已建立好连接的套接字或-1
sockfd
: 监听套接字addr
:对方地址
addrlen
:地址长度
7、connect函数
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
返回值:0 或 -1
sockfd
: socket返回的文件描述符serv_addr
:服务器端的地址信息
addrlen
:serv_addr
的长度
注:
connect()
是客户端使用的系统调用。
listen()
和accept()
是TCP服务器端使用的函数
8、send函数
#include <sys/socket.h>
ssize_t send(int socket, const void *buffer, size_t length, int flags);
返回值:
成功
:实际发送的字节数
,失败
:-1
, 并设置errno
buffer
: 发送缓冲区首地址length
: 发送的字节数flags
: 发送方式(通常为0)
一般填写0,此时和write()作用一样
特殊的标志:
*MSG_DONTWAIT
: Enables nonblocking operation;非阻塞版本
*MSG_OOB
:用于发送TCP类型的带外数据
(out-of-band)
9、recv函数
#include <sys/socket.h>
ssize_t recv(int socket, const void *buffer, size_t length, int flags);
返回值:
成功
:实际接收的字节数
,失败
:-1
, 并设置errno
buffer
: 发送缓冲区首地址length
: 发送的字节数flags
: 接收方式(通常为0)MSG_DONTWAIT
: Enables nonblocking operation;非阻塞版本
MSG_OOB
:用于发送TCP类型的带外数据
(out-of-band)MSG_PEEK
:
接收消息的开头,但不删除该消息数据。 因此,随后的接收将
返回相同的数据
10、read()/write()
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
- read()和write()经常会代替recv()和send()
- 通常情况下,看程序员的偏好
使用read()/write()和recv()/send()时最好统一
11、套接字的关闭
int close(int sockfd); //关闭双向通讯
int shutdown(int sockfd, int howto);
TCP连接
是双向的(是可读写的)
,当我们使用close
时,会把读写通道都关闭
,有时侯我们希望只关闭一个方向
,这个时候我们可以使用shutdown
。- 针对不同的howto,系统回采取不同的关闭方式。
howto = 0
关闭读通道,但是可以继续往套接字写数据。howto = 1
和上面相反,关闭写通道。只能从套接字读取数据。howto = 2
关闭读写通道,和close()一样
示例
客机向服务器写数据,服务器读数据并打印
net.h-包含的头文件以及宏定义
/*************************************************************************
> File Name: net.h
> Author: xiuchengzhen
> CSDN: xiuchengzhen.blog.csdn.net
> Created Time: Tue 29 Mar 2022 05:45:13 AM PDT
************************************************************************/
#ifndef NET_H
#define NET_H
#include<stdio.h>
#include<stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include<unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include<errno.h>
#define SER_PORT 5001 //端口号
#define SER_ADDR "192.168.125.128" //ip地址
#define BUFSIZE 15 //发送数据的大小(字节)
#define USER_QUIT "quit" (退出命令)
#endif
服务器sever.c:
/*************************************************************************
> File Name: sever.c
> Author: xiuchengzhen
> CSDN: xiuchengzhen.blog.csdn.net
> Created Time: Tue 29 Mar 2022 01:41:36 AM PDT
************************************************************************/
#include "net.h"
int main(int argc, const char *argv[])
{
/********socket文件创建***********/
int fd = socket( AF_INET, SOCK_STREAM, 0);
if(fd < 0)
{
perror("socket");
exit(-1);
}
/*******bind设置*******/
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin)); //清零操作
sin.sin_family = AF_INET; //地址族
sin.sin_port = htons(SER_PORT);
#if 0
sin.sin_addr = inet_addr(SER_ADDR);
#else
if(inet_pton(AF_INET, SER_ADDR, (void *)&sin.sin_addr) == 0)
{
perror("inet_pton:");
exit(-1);
}
#endif
if(bind(fd, (struct sockaddr *)&sin,sizeof(sin)) < 0)
{
perror("bind:");
exit(-1);
}
/********listen*********/
if(listen(fd, 5) < 0)
{
perror("listen:");
exit(-1);
}
/*******accept阻塞接收*********/
int newfd;
if((newfd = accept(fd, NULL, NULL)) < 0)
{
perror("accept:");
exit(-1);
}
/******read*******/
char buf[BUFSIZE];
int ret = -1;
while(1)
{
bzero((void *)buf, BUFSIZE); //清零
do
{
ret = read(newfd, (void *)buf, BUFSIZE-1);
}while(ret < 0 && EINTR == errno); //没读到就一直读
if(ret < 0) //读错误
{
perror("read");
exit(-1);
}
if(ret == 0) //连接断开
{
printf("client break link!\n");
break;
}
printf("re:%s\n", buf);
if(strncasecmp(buf, USER_QUIT, strlen(USER_QUIT)) == 0)
{
printf("client choice break link!\n");
break;
}
}
/*********关闭网络***********/
close(newfd);
close(fd);
return 0;
}
客机client.c:
/*************************************************************************
> File Name: client.c
> Author: xiuchengzhen
> CSDN: xiuchengzhen.blog.csdn.net
> Created Time: Tue 29 Mar 2022 05:43:55 AM PDT
************************************************************************/
#include "net.h"
int main(int argc, const char *argv[])
{
/********socket文件创建***********/
int fd = socket( AF_INET, SOCK_STREAM, 0);
if(fd < 0)
{
perror("socket");
exit(-1);
}
/*******connect设置*******/
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin)); //清零操作
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT);
#if 0
sin.sin_addr = inet_addr(SER_ADDR);
#else
if(inet_pton(AF_INET, SER_ADDR, (void *)&sin.sin_addr) == 0)
{
perror("inet_pton:");
exit(-1);
}
#endif
if(connect(fd, (struct sockaddr *)&sin,sizeof(sin)) < 0)
{
perror("connect:");
exit(-1);
}
printf("client connect success!\n");
char buf[BUFSIZE] = {0};
int ret = -1;
while(1)
{
bzero(buf, 0);
if(fgets(buf, BUFSIZE-1, stdin) == 0)
{
continue;
}
do
{
ret = write(fd, buf, strlen(buf));
}while(ret < 0 && EINTR == errno);
if (!strncasecmp (buf, USER_QUIT, strlen (USER_QUIT)))
{
printf ("Client is exiting!\n");
break;
}
}
return 0;
}
运行结果:
二、并发编程
主要是应对当多个客机对服务器进行连接的情况
。
- 当一个客机与服务器连接时,服务器创建一个线程或一个子进程为客机单独服务,主函数继续等待新的客机连接
1、多线程并发
实现过程在上面的例子上稍微改进
头文件net.h:
/*************************************************************************
> File Name: net.h
> Author: xiuchengzhen
> CSDN: xiuchengzhen.blog.csdn.net
> Created Time: Tue 29 Mar 2022 05:45:13 AM PDT
************************************************************************/
#ifndef NET_H
#define NET_H
#include<stdio.h>
#include<stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include<unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include<errno.h>
#include <netinet/in.h>
#include<pthread.h>
#define SER_PORT 5001
#define SER_ADDR "192.168.125.128"
#define BUFSIZE 15
#define USER_QUIT "quit"
#define BACK_LOG 5
#endif
服务器sever.c:
/*************************************************************************
> File Name: sever.c
> Author: xiuchengzhen
> CSDN: xiuchengzhen.blog.csdn.net
> Created Time: Tue 29 Mar 2022 01:41:36 AM PDT
************************************************************************/
#include "net.h"
void *client_hander(void *arg);
int main(int argc, const char *argv[])
{
int i = 0;
/********socket文件创建***********/
int fd = socket( AF_INET, SOCK_STREAM, 0);
if(fd < 0)
{
perror("socket");
exit(-1);
}
/*******bind设置*******/
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin)); //清零操作
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT);
/***********优化1**********/
#if 1
sin.sin_addr.s_addr = htonl(INADDR_ANY); //当系统IP地址发生变化时,这里的地址也会随着变化
#else
if(inet_pton(AF_INET, SER_ADDR, (void *)&sin.sin_addr) == 0)
{
perror("inet_pton:");
exit(-1);
}
#endif
if(bind(fd, (struct sockaddr *)&sin,sizeof(sin)) < 0)
{
perror("bind:");
exit(-1);
}
/********listen********/
if(listen(fd, BACK_LOG) < 0) //设置为服务器模式,与最大接收数
{
perror("listen:");
exit(-1);
}
/*********多线程优化*********/
/*******accept阻塞接收*********/
while(1)
{
#if 0
int newfd;
if((newfd = accept(fd, NULL, NULL)) < 0)
{
perror("accept:");
exit(-1);
}
#else
int newfd[BACK_LOG];
struct sockaddr_in cin;
char client_ip[16] = {0};
socklen_t cin_len = (socklen_t)sizeof(cin);
memset(&cin, 0, sizeof(cin));
if((newfd[i] = accept(fd, (struct sockaddr *)&cin, &cin_len)) < 0) //阻塞接收
{
perror("accept:");
exit(-1);
}
if(inet_ntop(AF_INET, (void *)&cin.sin_addr, client_ip, sizeof(cin)) == NULL)
{
perror("inet_ntop:");
exit(-1);
}
printf("client Port:%d,IP:%s\n",ntohs(cin.sin_port), client_ip);
#endif
pthread_t tid;
if(pthread_create(&tid, NULL, client_hander, (void *)&newfd[i]) != 0)
{
printf("tid create error!\n");
exit(-1);
}
i++;
}
close(fd);
return 0;
}
void *client_hander(void *newfd)
{
pthread_detach(pthread_self()); //设置线程分离属性,线程结束时自动回收
/******read*******/
char buf[BUFSIZE];
int ret = -1;
while(1)
{
bzero((void *)buf, BUFSIZE);
do
{
ret = read(*(int *)newfd, (void *)buf, BUFSIZE-1);
}while(ret < 0 && EINTR == errno);
if(ret < 0)
{
perror("read");
exit(-1);
}
if(ret == 0)
{
printf("fd:%d client break link!\n", *(int *)newfd);
break;
}
printf("fd:%d read:%s\n", *(int *)newfd, buf);
if(strncasecmp(buf, USER_QUIT, strlen(USER_QUIT)) == 0)
{
printf("fd:%d client choice break link!\n", *(int *)newfd);
break;
}
}
/*********关闭网络***********/
close(*(int *)newfd);
return NULL;
}
客机client.c:
/*************************************************************************
> File Name: client.c
> Author: xiuchengzhen
> CSDN: xiuchengzhen.blog.csdn.net
> Created Time: Tue 29 Mar 2022 05:43:55 AM PDT
************************************************************************/
#include "net.h"
int main(int argc, const char *argv[])
{
if(argc < 2)
{
printf("Please enter the port and address at run time!\n"); // ./运行文件 端口号 IP
printf("For example:\n ./ file port IP\n");
exit(-1);
}
/********socket文件创建***********/
int fd = socket( AF_INET, SOCK_STREAM, 0);
if(fd < 0)
{
perror("socket");
exit(-1);
}
/*******connect设置*******/
struct sockaddr_in sin;
int port = atoi(argv[1]);
if(port < 5000)
{
printf("The port number must be smaller than 5000\n");
exit(-1);
}
memset(&sin, 0, sizeof(sin)); //清零操作
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
#if 0
sin.sin_addr = inet_addr(argv[2]);
#else
if(inet_pton(AF_INET, argv[2], (void *)&sin.sin_addr) == 0)
{
perror("inet_pton:");
exit(-1);
}
#endif
if(connect(fd, (struct sockaddr *)&sin,sizeof(sin)) < 0)
{
perror("connect:");
exit(-1);
}
printf("client connect success!\n");
char buf[BUFSIZE] = {0};
int ret = -1;
while(1)
{
bzero(buf, 0);
if(fgets(buf, BUFSIZE-1, stdin) == 0)
{
continue;
}
do
{
ret = write(fd, buf, strlen(buf));
}while(ret < 0 && EINTR == errno);
if (!strncasecmp (buf, USER_QUIT, strlen (USER_QUIT)))
{
printf ("Client is exiting!\n");
break;
}
}
return 0;
}
运行结果:
2、多进程并发
只需在服务器上改动,其他不变:
/*************************************************************************
> File Name: sever_process.c
> Author: xiuchengzhen
> CSDN: xiuchengzhen.blog.csdn.net
> Created Time: Tue 29 Mar 2022 01:41:36 AM PDT
************************************************************************/
#include "net.h"
void process_hander(int log);
void client_hander(int arg);
int main(int argc, const char *argv[])
{
signal(SIGCHLD, process_hander);
/********socket文件创建***********/
int fd = socket( AF_INET, SOCK_STREAM, 0);
if(fd < 0)
{
perror("socket");
exit(-1);
}
/*******bind设置*******/
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin)); //清零操作
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT);
/***********优化1**********/
#if 1
sin.sin_addr.s_addr = htonl(INADDR_ANY); //当系统IP地址发生变化时,这里的地址也会随着变化
#else
if(inet_pton(AF_INET, SER_ADDR, (void *)&sin.sin_addr) == 0)
{
perror("inet_pton:");
exit(-1);
}
#endif
if(bind(fd, (struct sockaddr *)&sin,sizeof(sin)) < 0)
{
perror("bind:");
exit(-1);
}
/********listen********/
if(listen(fd, BACK_LOG) < 0) //设置为服务器模式,与最大接收数
{
perror("listen:");
exit(-1);
}
/*********多进程优化*********/
/*******accept阻塞接收*********/
while(1)
{
#if 0
int newfd;
if((newfd = accept(fd, NULL, NULL)) < 0)
{
perror("accept:");
exit(-1);
}
#else
int newfd;
struct sockaddr_in cin;
char client_ip[16] = {0};
socklen_t cin_len = (socklen_t)sizeof(cin);
memset(&cin, 0, sizeof(cin));
if((newfd = accept(fd, (struct sockaddr *)&cin, &cin_len)) < 0) //阻塞接收
{
perror("accept:");
exit(-1);
}
if(inet_ntop(AF_INET, (void *)&cin.sin_addr, client_ip, sizeof(cin)) == NULL)
{
perror("inet_ntop:");
exit(-1);
}
printf("client Port:%d,IP:%s\n",ntohs(cin.sin_port), client_ip);
#endif
pid_t pid = fork();
if(pid < 0)
{
perror("fork:");
exit(-1);
}
else if(pid == 0)
{
close(fd);
client_hander(newfd);
return 0;
}
else
close(newfd); //父进程关闭,减少资源浪费
}
close(fd);
return 0;
}
void process_hander(int log)
{
if(log == SIGCHLD)
wait(NULL);
}
void client_hander(int newfd)
{
pthread_detach(pthread_self()); //设置线程分离属性,线程结束时自动回收
/******read*******/
char buf[BUFSIZE];
int ret = -1;
int pid = getpid();
while(1)
{
bzero((void *)buf, BUFSIZE);
do
{
ret = read(newfd, (void *)buf, BUFSIZE-1);
}while(ret < 0 && EINTR == errno);
if(ret < 0)
{
perror("read");
exit(-1);
}
if(ret == 0)
{
printf("pid:%d client break link!\n", pid);
break;
}
printf("pid:%d read:%s\n", pid, buf);
if(strncasecmp(buf, USER_QUIT, strlen(USER_QUIT)) == 0)
{
printf("pid:%d client choice break link!\n", pid);
break;
}
}
/*********关闭网络***********/
close(newfd);
}
运行结果
到这里就结束啦!