socket编程接口:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
说明:socket()函数用于建立一个新的socket通信,也就是向系统注册,通知系统建一通讯端口。
使用场景:TCP/UDP、客户端/服务器端。
参数:
- domain:指定使用何种网络地址类型,常用的地址类型有AF_INET(IPv4网络协议)、AF_INET6(IPv6网络协议)。
- type:指定使用何种应用层协议连接,常用的选项有SOCK_STREAM(双向、连续、可靠、基于字节流的TCP连接)、SOCK_DGRAM(双向、不连续、不可考、基于数据包的UDP连接)。
- protocal:此参数用于指定socket所使用的传输协议编号,通常此参数不使用,置为0即可。
返回值:成功,返回创建的socket套接字,失败,返回-1并设置errno。
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr,
socklen_t addrlen);
说明:bind为套接字sockfd指定本地地址,my_addr指定本地地址,my_addr的长度为addrlen(字节),传统的叫法是给一个套接字分配一个名字,当使用socke()创建一个套接字时,它存在于一个地址空间(地址族)。但还没有为它分配一个名字,一般来说在使用套接字之前总会使用bind为其分配一个本地地址,可以理解为让sockfd绑定一个本地ip地址和端口号。
使用场景:TCP/UDP、服务器端。
参数:
- sockfd:socket()函数返回的套接字
- my_addr:指向struct sockaddr结构体,此结构是一个通用的结构体,对于不同的网络地址类型各自使用不同的结构体存储,在传参时强转成通用的struct sockaddr结构体指针。例如我们常用的AF_INET网络地址类型时的结构体为struct sockaddr_in,以上两个结构体的定义如下所示:
//在usr/include/socket.h中定义
/* Structure describing a generic socket address. */
struct sockaddr
{
__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
//在usr/include/netinet/in.h中定义
/* Internet address. */
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
返回值:成功,返回0,失败,返回-1,并设置errno。
#include <sys/socket.h>
int listen(int s, int backlog);
说明:使socket()创建的主动连接套接字变为被动连接套接字,处于监听状态,能够自动接收到来的连接请求。backlog参数指定已连接队列的长度。
使用场景:TCP 服务器端。
参数:
- s:要改变的套接字
- backlog:Linux中TCP协议实现时会保存两个队列,一个队列保存正在建立的连接,另一个保存已完成建立的连接但没有被accept()函数拿走的连接,backlog参数指定已完成建立的队列大小,而正在建立队列的大小由系统参数决定。更多关于各种队列连接数达到上限的问题,大家可以上网查找资料,在此不过多涉猎。
返回值:成功,返回0,失败,返回-1,并设置errno。
#include <sys/types.h>
#include <sys/socket.h>
int accept(int s, struct sockaddr *addr,
socklen_t *addrlen);
说明:此函数用于从listen()改变成监听状态的套接字中拿到一个已完成连接。
使用场景:TCP 服务器端
参数:
- s:监听套接字。
- addr:用于存储对端网络地址(IP地址和端口号)的缓冲区。
- addrlen:输入输出参数,输入时,表示缓冲区的大小,输出时,表示存储对端网络地址结构体的大小。
返回值:成功,返回一个新的连接套接字,此连接套接字用于专门操作本次连接,失败,返回-1,并设置errno。
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
说明:此函数用来将sockfd的socket连接到addr指定的远端网络地址。
使用场景:TCP 客户端
参数:
- sockfd:socket()函数产生的套接字。
- addr:远端服务器的网络地址。
- addrlen:addr参数的长度。
返回值:成功,返回0,失败,返回-1,并设置errno。
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
说明:系统调用read()函数。
使用场景:TCP 客户/服务器端
#include <unistd.h>
ssize_t write(int fd, const void *buf,
size_t count);
说明:系统调用write()函数。
使用场景:TCP 客户/服务器端。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len,
int flags, struct sockaddr *src_addr,
socklen_t *addrlen);
说明:本函数用来接收远程主机经指定的socket 传来的数据,并捕获数据发送源的地址。
使用场景:UDP 客户端/服务器端
参数:
- sockfd:套接口,用于从此套接口上接收数据。
- buf:接收数据缓冲区。
- len:buf的长度。
- flags:通过flags的取值可以改变函数的执行方式,常有三种可能取值:MSG_PEEK,查看当前数据,数据将被复制到缓冲区中,但并不从输入队列中删除;MSG_OOB,处理带外数据;0,一般情况下都设为0,表示什么都不改变。
- src_addr:缓冲区,用于接收数据发送源的地址。
- addrlen:输入输出型参数,输入时表示src_addr指向缓冲区的大小,输出时表示实际地址的大小。
返回值:若无错误发生,返回读入的字节数,如果连接已终止,返回0,否则,返回-1,错误原因存于errno。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len,
int flags, const struct sockaddr *dest_addr,
socklen_t addrlen);
说明:本函数用于将数据经指定的socket传送给对方主机。
使用场景:UDP 客户端/服务器端
参数:
- sockfd:已建立连接的套接口,若是UDP,则不用建立连接。
- buf:发送数据缓冲区。
- len:buf的长度。
- flags:一般置为0。
- dest_addr:指向发送数据的目标地址。
- addrlen:dest_addr所指向结构的长度。
返回值:成功,返回发送的字节数
网络字节序:
我们都知道内存中的多字节数据对于内存地址有大、小端之分,磁盘文件中的多字节数据相对文件偏移指针也有大、小端之分,同样网络数据流也有大、小端之分,那么网络字节流的地址应该如何让定义?
其实,发送主机发送数据时是按照发送缓冲区的地址有小到大依次发送的,接收主机接收数据时也会将接收到的数据按照接收缓冲区的地址由小到大存储的。所以,网络数据流的地址规定为:先发出的数据为低地址,后发出的数据为高地址。
TCP/IP协议规定:网络数据流应采用大端字节序,即低地址高字节。那么,当发送主机或接收主机为小端字节序时,就需要将先将数据转换为大端字节序再发送或者将网络数据转换为小端字节序再存储。
为了方便编程与程序的可移值性,在编程时我们应该使用下面的库函数:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
说明:h
表示主机序,n
表示网络序,l
表示长整形,s
表示短整形。htonl()表示将长整形主机字节序转换为网络字节序,如果主机为大端字节序则参数不转换,原样返回,否则,函数返回转换后的大端字节序。
地址转换函数:
struct sockaddr_in结构中的struct in_addr表示32位无符号整数的IP地址,但我们常常习惯用点分十进制的字符串表示IP地址,那么该怎么办呢?原来库中已经为我们封装好了许多用于字符常IP和无符号整形IP间互相转换的函数,如下所示:
struct in_addr到字符串的转换:
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
int inet_pton(int af, const char *src, void *dst);
说明:inet_aton()函数将cp所指向的点分十进制IP地址转换后存储在inp所指向的结构体中,inet_addr()将cp所指向的点分十进制IP地址转换后直接返回,注意此处为in_addr_t类型,struct in_addr结构体中的类型
,inet_pton()按照af所指定的网络地址格式(可以是IPv4或IPv6),将src所指向的点分十进制字符串IP地址转换后存储在dst指向的相应结构体(IPv4为struct in_addr)中。
字符串到struct in_addr的转换:
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);
const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size);
说明:inet_ntoa()函数将in指向的无符号整形的IP地址转换为点分十进制的字符串IP后存储在函数内部开辟的静态区域并返回静态区首地址,注意此处有坑,当调用函数第二次时会覆盖掉第一次的结果
。inet_ntop()函数根据af所指定的网络地址类型,将src指向的无符号整数的IP地址转换后存储在dst所指向的缓冲区中,size表示缓冲区的大小。
UDP程序:
client.c
#include<unistd.h>
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<string.h>
#include<netinet/in.h>
int main(int argc,char *argv[]){
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0){
perror("socket");
return -1;
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
char buf[1024];
struct sockaddr_in peer;
while(1){
socklen_t len = sizeof(peer);
printf("请输入#");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s > 0){
buf[s] = 0;
sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&server,sizeof(server));
int ss = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len);
if(ss > 0){
buf[ss] = 0;
printf("server echo$%s",buf);
}
}
}
return 0;
}
server.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<string.h>
#include<arpa/inet.h>
int main(int argc,char *argv[]){
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0){
perror("socket");
return -1;
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[2]));
local.sin_addr.s_addr = inet_addr(argv[1]);
if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){
perror("bind");
return -2;
}
char buf[1024];
struct sockaddr_in client;
while(1){
socklen_t len = sizeof(client);
ssize_t s = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&client,&len);
if(s > 0){
buf[s] = 0;
//printf("haha\n");
printf("[%s:%d]:%s",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);
sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&client,sizeof(client));
}
}
return 0;
}
结果展示:
TCP程序:
单连接版本:
client.c
#include<stdio.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<string.h>
int main(int argc,char *argv[]){
if(argc != 3){
printf("Usege method:./progra_name ip port\n");
return -1;
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
perror("socket");
return -2;
}
struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_addr.s_addr = inet_addr(argv[1]);
server_sockaddr.sin_port = htons(atoi(argv[2]));
int ret = connect(sock,(struct sockaddr*)&server_sockaddr,sizeof(server_sockaddr));
if(ret < 0){
perror("connect");
return -3;
}
while(1){
char buf[1024] = {0};
printf("client#");
fflush(stdout);
read(0,buf,sizeof(buf)-1);
write(sock,buf,strlen(buf)+1);
memset(buf,'\0',sizeof(buf));
read(sock,buf,sizeof(buf)-1);
printf("server#%s",buf);
}
close(sock);
return 0;
}
server.c
#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#define _BACKLOG 10
int main(int argc,char *argv[]){
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
perror("socket");
return -1;
}
struct linger so_linger;
so_linger.l_onoff = 1;
so_linger.l_linger = 0;
int ret = setsockopt(sock,SOL_SOCKET,SO_LINGER,&so_linger,sizeof(so_linger));
if(ret < 0){
perror("setsockopt");
return -5;
}
struct sockaddr_in server_sockaddr;
struct sockaddr_in client_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_addr.s_addr = inet_addr(argv[1]);
server_sockaddr.sin_port = htons(atoi(argv[2]));
if(bind(sock,(struct sockaddr*)&server_sockaddr,sizeof(server_sockaddr)) < 0){
perror("bind");
return -2;
}
if(listen(sock,_BACKLOG) < 0){
perror("listen");
return -3;
}
while(1){
socklen_t len = sizeof(client_sockaddr);
int client_sock = accept(sock,(struct sockaddr*)&client_sockaddr,&len);
if(client_sock < 0){
perror("accept");
return -4;
}
char ip[INET_ADDRSTRLEN] = {0};
inet_ntop(AF_INET,&client_sockaddr.sin_addr,ip,sizeof(ip));
printf("[%s:%d]\n",ip,ntohs(client_sockaddr.sin_port));
//printf("haha\n");
while(1){
char buf[1024] = {0};
read(client_sock,buf,sizeof(buf)-1);
printf("client:#%s",buf);
printf("server:$");
fflush(stdout);
memset(buf,'\0',sizeof(buf));
read(0,buf,sizeof(buf)-1);
write(client_sock,buf,strlen(buf)+1);
}
}
close(sock);
return 0;
}
结果展示:
多进程版本:
client.c
#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
int main(int argc,char *argv[]){
if(argc != 3){
printf("Usage method:./program ip port\n");
return -1;
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
perror("socket");
return -2;
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
server_addr.sin_port = htons(atoi(argv[2]));
int ret = connect(sock,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(ret < 0){
perror("connect");
return -3;
}
while(1){
char buf[1024] = {0};
printf("client#");
fflush(stdout);
read(0,buf,sizeof(buf)-1);
write(sock,buf,strlen(buf));
memset(buf,'\0',sizeof(buf));
read(sock,buf,sizeof(buf)-1);
printf("server$%s",buf);
}
return 0;
}
server.c
#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<string.h>
#include<stdlib.h>
#include<arpa/inet.h>
#define BACKLOG 100
void HandleRequest(int client_sock,struct sockaddr_in* client_addr){
char ip[INET_ADDRSTRLEN] = {0};
while(1){
char buf[1024] = {0};
ssize_t read_size = read(client_sock,buf,sizeof(buf)-1);
if(read_size < 0){
perror("server read");
continue;
}else if(read_size == 0){
printf("[%s:%d]client say baby\n",inet_ntop(AF_INET,&(client_addr->sin_addr),ip,sizeof(ip)),ntohs(client_addr->sin_port));
close(client_sock);
break;
}else{
printf("[%s:%d]client#%s",inet_ntop(AF_INET,&(client_addr->sin_addr),ip,sizeof(ip)),ntohs(client_addr->sin_port),buf);
write(client_sock,buf,strlen(buf));
}
}
}
void CreateWorker(int client_sock,struct sockaddr_in* client_addr){
pid_t id = fork();
if(id < 0){
perror("fork1");
exit(-1);
}else if(id > 0){
//grand_father
close(client_sock);
waitpid(id,NULL,0);
}else{
//father
id = fork();
if(id < 0){
perror("fork2");
exit(-2);
}else if(id > 0){
//father
close(client_sock);
exit(0);
}else{
//grand_child
HandleRequest(client_sock,client_addr);
}
}
}
int main(int argc,char *argv[]){
if(argc != 3){
printf("Usage method:./program ip prot\n");
return -1;
}
int sock = socket(AF_INET,SOCK_STREAM,0);
struct linger so_linger;
so_linger.l_onoff = 1;
so_linger.l_linger = 0;
int ret = setsockopt(sock,SOL_SOCKET,SO_LINGER,&so_linger,sizeof(so_linger));
if(sock < 0){
perror("socket");
return -2;
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
server_addr.sin_port = htons(atoi(argv[2]));
if(bind(sock,(struct sockaddr*)&server_addr,sizeof(server_addr)) < 0){
perror("bind");
return -3;
}
if(listen(sock,BACKLOG) < 0){
perror("listen");
return -4;
}
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
while(1){
int client_sock = accept(sock,(struct sockaddr*)&client_addr,&len);
if(client_sock < 0){
perror("accept");
}else{
CreateWorker(client_sock,&client_addr);
}
}
return 0;
}
结果展示:
多线程版本:
client.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<string.h>
#define BACKLOG 10
int main(int argc,char *argv[]){
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
perror("sock");
return -1;
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
server_addr.sin_port = htons(atoi(argv[2]));
int ret = connect(sock,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(ret < 0){
perror("connect");
return -2;
}
while(1){
char buf[1024] = {0};
printf("client#");
fflush(stdout);
read(0,buf,sizeof(buf)-1);
write(sock,buf,strlen(buf));
memset(buf,'\0',sizeof(buf));
int readsize = read(sock,buf,sizeof(buf)-1);
if(readsize < 0){
printf("client read fail\n");
continue;
}else if(readsize == 0){
close(sock);
break;
}else{
printf("[%s:%s]server$%s",argv[1],argv[2],buf);
}
}
return 0;
}
server.c
#include<string.h>
#include<stdlib.h>
#include<pthread.h>
#define BACKLOG 10
void Handleprocess(int client_sock,struct sockaddr_in client_addr){
char ip[INET_ADDRSTRLEN] = {0};
while(1){
char buf[1024] = {0};
ssize_t readsize = read(client_sock,buf,sizeof(buf)-1);
if(readsize < 0){
perror("server read");
continue;
}else if(readsize == 0){
printf("[%s:%d]client say baby\n",inet_ntop(AF_INET,(void *)&(client_addr.sin_addr),ip,sizeof(ip)),ntohs(client_addr.sin_port));
close(client_sock);
break;
}else{
printf("[%s:%d]clients#%s",inet_ntop(AF_INET,(void *)&(client_addr.sin_addr),ip,sizeof(ip)),ntohs(client_addr.sin_port),buf);
write(client_sock,buf,strlen(buf));
}
}
}
typedef struct Arg{
int client_sock;
struct sockaddr_in client_addr;
} Arg;
void* CreateWorker(void *ptr){
Arg* arg = (Arg*)ptr;
Handleprocess(arg->client_sock,arg->client_addr);
free(arg);
return NULL;
}
int main(int argc,char *argv[]){
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
perror("socket");
return -1;
}
struct linger so_linger;
so_linger.l_onoff = 1;
so_linger.l_linger = 0;
int ret = setsockopt(sock,SOL_SOCKET,SO_LINGER,(void *)&so_linger,sizeof(so_linger));
if(ret != 0){
printf("Call setsockopt error\n");
return -3;
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
server_addr.sin_port = htons(atoi(argv[2]));
if(bind(sock,(struct sockaddr*)&server_addr,sizeof(server_addr)) < 0){
perror("bind");
return -2;
}
if(listen(sock,BACKLOG) < 0){
perror("listen");
return -3;
}
while(1){
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int client_sock = accept(sock,(struct sockaddr*)&client_addr,&len);
if(client_sock < 0){
perror("accept");
continue;
}
Arg* arg = (Arg*)malloc(sizeof(Arg));
arg->client_sock = client_sock;
arg->client_addr = client_addr;
pthread_t tid;
pthread_create(&tid,NULL,CreateWorker,(void*)arg);
pthread_detach(tid);
}
return 0;
}
结果展示:
TCP/UDP对比:
属性 | TCP | UDP |
---|---|---|
连接状态 | 有连接 | 无连接 |
可靠性 | 可靠传输 | 不可靠传输 |
数据类型 | 面向字节流 | 面向数据报 |
netstat命令:
netstat命令是用来打印Linux中网络系统的状态信息。
语法:
netstat (选项)
选项 | 说明 |
---|---|
-a | 显示所有连接中的socket |
-l | 显示监控中的服务器的socket |
-s | 显示网络工作信息统计表 |
-c | 持续列出网络状态,每隔一秒 |
-n | 直接使用IP地址,而不通过域名服务器,可以加速输出,因为不用查找域名服务器进行主机、端口、用户名的转换 |
-r | 显示路由表 |
-p | 显示正在使用socket的程序识别码和程序名称 |
-t | 显示TCP传输协议的连接状态 |
-u | 显示UDP传输协议的连接状态 |