Linux网络编程基础-03_TCP/IP网络编程
网络编程相关API
- socket() 创建套接字
- bind() 绑定本机地址和端口
- connect() 建立连接
- listen() 设置监听套接字,把主动套接字变成动套接字
- accept() 接受TCP连接 ,阻塞等待客户端连接请求
- recv(), read(), recvfrom() 数据接收
- send(), write(),sendto() 数据发送
- close(), shutdown() 关闭套接字
socket
int socket (int domain, int type, int protocol);
-
domain 是地址族
PF_INET // internet 协议
PF_UNIX // unix internal协议
PF_NS // Xerox NS协议
PF_IMPLINK // Interface Message协议 -
type // 套接字类型
SOCK_STREAM // 流式套接字
SOCK_DGRAM // 数据报套接字
SOCK_RAW // 原始套接字 -
protocol 参数通常置为0
地址相关的数据结构
-
通用地址结构
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,作为填充 };
-
IPv4地址结构
// internet addressstruct in_addr { in_addr_t s_addr; // u32 network address };
bind
int bind (int sockfd, struct sockaddr* addr, int addrLen);
-
sockfd 由socket() 调用返回
-
addr 是指向 sockaddr_in 结构的指针,包含本机IP 地址和端口号
struct sockaddr_in
u_short sin_family // protocol family
u_short sin_port // port number
struct in_addr sin_addr //IP address (32-bits) -
addrLen : sizeof (struct sockaddr_in)
地址结构的一般用法
-
定义一个struct sockaddr_in类型的变量并清空 struct
sockaddr_in myaddr; memset(&myaddr, 0, sizeof(myaddr));
-
填充地址信息
myaddr.sin_family = PF_INET;
myaddr.sin_port = htons(8888);
myaddr.sin_addr.s_addr = inet_addr(“192.168.1.100”);
- 将该变量强制转换为struct sockaddr类型在函数中使用
bind(listenfd, (struct sockaddr*)(&myaddr), sizeof(myaddr));
地址转换函数
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 ");
char* inet_ntoa(struct in_addr address);
- address是IPv4地址结构,函数返回一指向包含点分IP地址的静态存储区字符指针。如果错误则函数返回NULL
listen
int listen (int sockfd, int backlog);
- sockfd:监听连接的套接字
- backlog
指定了正在等待连接的最大队列长度,它的作用在于处理可能同时出现的几个连接请求。
DoS(拒绝服务)攻击即利用了这个原理,非法的连接占用了全部的连接数,造成正常的连接请求被拒绝。 一般填5, 测试得知,ARM最大为8 - 返回值: 0 或 -1
- 内核中服务器的套接字fd会维护2个链表:
- 正在三次握手的的客户端链表(数量=2*backlog+1)
- 已经建立好连接的客户端链表(已经完成3次握手分配好了newfd)
- 比如:listen(fd, 5); //表示系统允许11(=2*5+1)个客户端同时进行三次握手
accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) ;
- 返回值:已建立好连接的套接字或-1
- 头文件
#include <sys/types.h>
#include <sys/socket.h> - sockfd : 监听套接字
- addr : 对方地址
- addrlen:地址长度
connect()
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
- 返回值:0 或 -1
- 头文件:
#include <sys/types.h>
#include <sys/socket.h> - sockfd : socket返回的文件描述符
- serv_addr : 服务器端的地址信息
- addrlen : serv_addr的长度
send()
ssize_t send(int socket, const void *buffer, size_t length, int flags);
- 返回值:
成功:实际发送的字节数
失败:-1, 并设置errno - 头文件:
#include <sys/socket.h> - buffer : 发送缓冲区首地址
- length : 发送的字节数
- flags : 发送方式(通常为0)
recv()
ssize_t recv(int socket, const void *buffer, size_t length, int flags);
返回值:
成功:实际接收的字节数
失败:-1, 并设置errno
头文件:
#include <sys/socket.h>
buffer : 发送缓冲区首地址
length : 发送的字节数
flags : 接收方式(通常为0)
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()时最好统一
sendto(),recvfrom()
ssize_t sendto(int socket, void *message, size_t length, int flags, struct sockaddr *dest_addr, socklen_t dest_len);
ssize_t recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *address, socklen_t *address_len);
- 这两个函数一般在使用UDP协议时使用
套接字的关闭
int close(int sockfd);
关闭双向通讯
int shutdown(int sockfd, int howto);
- TCP连接是双向的(是可读写的),当我们使用close时,会把读写通道都关闭,有时侯我们希望只关闭一个方向,这个时候我们可以使用shutdown。
- 针对不同的howto,系统回采取不同的关闭方式。
- shutdown()的howto参数
howto = 0
关闭读通道,但是可以继续往套接字写数据。
howto = 1
和上面相反,关闭写通道。只能从套接字读取数据。
howto = 2
关闭读写通道,和close()一样
TCP编程API
示例:
server.c
#include "Net.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
int socket_fd = -1;
int fd_w = -1;
if ((fd_w = open("read.txt", O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0666)) < 0)
{
perror("open");
exit(1);
}
//Step1:创建socket 文件描述符
if ((socket_fd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
{
perror ("socket");
exit (1);
}
//连接服务器
struct sockaddr_in sin;
bzero (&sin, sizeof (sin));
sin.sin_family = AF_INET;
sin.sin_port = htons (SERV_PORT); //网络字节序的端口号
//把ip地址转化为用于网络传输的二进制数值
if( inet_pton(AF_INET, SERV_IP_ADDR, (void *)&sin.sin_addr) != 1)
{
perror("inet_pton");
exit(1);
}
//Step2-绑定
if( bind(socket_fd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
perror("bind");
exit(1);
}
//Step3-调用listen()
if( listen(socket_fd, BACKLOG) < 0)
{
perror("listen");
exit(1);
}
printf(" Server Starting... OK!\n ");
int new_sockfd = -1;
//通过程序获取刚建立连接的socket的客户端的IP地址和端口号
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
//Step4:接受TCP连接
if((new_sockfd = accept(socket_fd, (struct sockaddr*)&cin, &addrlen)) < 0)
{
perror("accept");
exit(1);
}
char ipv4_addr[16];
//将网络传输的二进制数值转化为成点分十进制的ip地址
if( !inet_ntop(AF_INET, (void*)&cin.sin_addr, ipv4_addr, sizeof(cin)))
{
perror("inet_ntop");
exit(1);
}
//用于将主机 unsigned short 型数据转换成网络字节顺序
printf(" Client(%s:%d ) is connected!\n",ipv4_addr, ntohs(cin.sin_port));
printf("Wait Client Send Data\n");
//step5:进行数据读写
int ret_sock = -1;
int ret_write = -1;
int ret_read = -1;
char buf[BUFSIZ];
char send_buf[BUFSIZ];
char display_buf[BUFSIZ];
while(1)
{
#if 1
bzero(buf, BUFSIZ);
bzero(send_buf, BUFSIZ);
bzero (display_buf, BUFSIZ);
do{
ret_read = read(new_sockfd, buf, BUFSIZ -1);
}while(ret_read < 0 && EINTR == errno );
if(ret_read < 0)
{
perror("read");
exit (1);
}
if(!ret_read)
{
break;
}
printf("Receive data: %s\n", buf);
if( !strncasecmp(buf, QUIT_STR, strlen(QUIT_STR)))
{
printf("Client is exiting!\n ");
break;
}
printf("Server Input data: ");
if(fgets (send_buf, BUFSIZ - 1, stdin) == NULL)
{
continue;
}
if( !strncasecmp(send_buf, QUIT_STR, strlen(QUIT_STR)))
{
printf("Server is exiting!\n ");
break;
}
do {
ret_sock = write (new_sockfd, send_buf, strlen (send_buf));
//sprintf(display_buf,"Client: %s",buf);
sprintf(display_buf,"Server: %s",send_buf);
ret_write = write(fd_w, display_buf, strlen(display_buf));
} while (ret_sock < 0 && EINTR == errno && ret_write < 0);
#endif
}
//Step6:关闭套接字
close(new_sockfd);
close(socket_fd);
exit(0);
}
Client.c
#include "Net.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
int socket_fd = -1;
int fd_w = -1;
if ((fd_w = open("read.txt", O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0666)) < 0)
{
perror("open");
exit(1);
}
//Step1:创建socket 文件描述符
if((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket");
exit(1);
}
//连接服务器
struct sockaddr_in sin;
bzero(&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);//将主机字节序转化成网络字节序
//把ip地址转化为用于网络传输的二进制数值
if( inet_pton(AF_INET, SERV_IP_ADDR, (void*)&sin.sin_addr) != 1)
{
perror("inet_pton");
exit(1);
}
//Step2:建立连接
if (connect (socket_fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
perror ("connect");
exit (1);
}
printf ("Client staring...OK!\n");
//Step3:进行数据读写
int ret_sock = -1;
int ret_write = -1;
int ret_read = -1;
char buf[BUFSIZ];
char rece_buf[BUFSIZ];
char display_buf[BUFSIZ];
while(1)
{
#if 1
bzero (buf, BUFSIZ);
bzero (rece_buf, BUFSIZ);
bzero (display_buf, BUFSIZ);
printf("Client Input data: ");
if (fgets (buf, BUFSIZ - 1, stdin) == NULL)
{
continue;
}
do {
ret_sock = write (socket_fd, buf, strlen (buf));
sprintf(display_buf,"Client: %s",buf);
ret_write = write(fd_w, display_buf, strlen(display_buf));
} while (ret_sock < 0 && EINTR == errno && ret_write < 0);
if (!strncasecmp (buf, QUIT_STR, strlen (QUIT_STR))) { //用户输入了quit字符
printf ("Client is exiting!\n");
break;
}
#endif
//------------------------------------------------------------------
do{
ret_read =read(socket_fd, rece_buf, BUFSIZ -1);
}while(ret_read < 0 && EINTR == errno );
if(ret_read < 0)
{
perror("read");
exit (1);
}
if(!ret_read)
{
break;
}
printf("Client Receive data: %s\n", rece_buf);
if( !strncasecmp(rece_buf, QUIT_STR, strlen(QUIT_STR)))
{
printf("Server is exiting!\n ");
break;
}
}
//Step3:关闭套接字
close(fd_w);
close(socket_fd);
exit(0);
}