在日常应用中有很多关于socket网络通信的例子,例如局域网内打游戏,使用浏览器看视频,用QQ软件聊天等。可以说socket是底层抽象给应用层所使用的一套接口。网络通信的传输方式有两种,一种是基于TCP(数据可靠传输),另一种是基于UDP(数据不可靠,一般用于实时视频传输)。
1.基于TCP的网络编程
由于基于TCP的套接字是面向连接的,因此又称为基于流(Stream)的套接字。TCP是Transmission Control Protocol(传输控制协议)的简写,译为“对数据传输过程的控制”。那么,在网络交互过程中,服务器端和客户端要始终保持连接,不能断开。TCP协议会重发一切出错数据,保证数据的完整性和顺序性。缺点就是消耗资源比较大。
服务器编程步骤:
(1)创建socket描述符socket()。
(2)准备通信地址 struct sockaddr_in。
(3)绑定bind()。
(4)监听listen()。
(5)等待客户端的连接accept()。
(6)read/write。
(7)关闭socket。
客户端编程步骤:
(1) 创建socket描述符socket()。
(2)准备通信地址struct sockaddr_in。
(3)连接到服务器connect()。
(4)read/write。
(5)关闭socket。
2.相关API讲解
1.inet_aton函数
函数原型:inet_aton(Const char *cp,struct in_addr *inp);
参数cp: 字符串类型的IP地址。
inp: struct in_addr*类型的数据。
函数作用:将字符串类型的cp转换成struct in_addr*类型的数据并赋值给inp变量。
返回值:成功则返回非0值,失败则返回0。
2.listen函数
函数原型:int listen(int sockfd, int backlog);
参数说明:
sockfd: socket描述符。
backlog: 未决连接请求队列的最大长度,即最多允许同时有多少个未决连接请求存在。在进程正在处理一个连接请求时,可能还存在其他连接请求。因为TCP连接是一个过程,所以可能存在一种半连接状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果出现这种情况,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理的连接(还没有调用accept函数的连接),这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。若服务器端的未决连接数已达此限值,此时,如果有客户端使用函数connect()连接服务器,那么connect()函数就会返回-1,errno的值为ECONNREFUSED。
返回值:成功则返回0,失败则返回-1。
3.accept函数
函数原型:int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
从sockfd所标识的未决连接请求队列中,提取一个连接请求,同时创建一个新的套接字用于在该连接中通信,返回值为该套接字的描述符。通常情况下如果连接请求队列中没有请求,accept会阻塞等待。
参数说明:
sockfd: 套接字描述符。
addr和addrlen:用于输出连接请求发起者的地址信息,注意这两个参数一定不能为空。
返回值:成功则返回通信套接字描述符,失败则返回-1。
4.recv函数
函数原型:ssize_t recv(int sockfd,void *buf,size_t len, int flags);
参数说明:
sockfd: 套接字描述符
buf, len:接收len个字节到buf所指向的缓冲区中。
flags: 通常情况下设置为0,表示没有数据读取时,客户端进程处于阻塞等待状态。
返回值:成功则返回实际接收到的字节数,失败则返回-1。
5.send函数
函数原型:size_t send(int sockfd,const void *buf,size_t len, int flags);
参数说明:
sockfd: 套接字描述符。
buf, len: 发送len个字节到buf所指向的缓冲区中。
flags: 通常情况设置为0,表示没有数据需要发送时,客户端进程处于阻塞等待状态。
返回值:成功则返回实际发送的字节数,失败则返回-1。
6.recvfrom函数
函数原型:ssize_t recvfrom(int sockfd,void *buf,size_t len,int flags,struct sockaddr *src_addr,socklen_t *addrlen)
参数说明:
sockfd: 套接字描述符
buf: 接收数据缓冲区
len: 期望接收数据长度。
flags: 默认取0。
src_addr: 获取客户端IP。
addrlen: 前一个参数对应结构体的大小,切记该值不取0。
返回值:成功则返回实际发送的字节数,失败则返回-1。
7.sendto函数
函数原型:ssize_t sendto(int sockfd,void *buf,size_t len,int flags,struct sockaddr *dest_addr, socklen_addrlen);
参数说明:
sockfd: 套接字描述符。
buf: 要发送数据的缓冲区。
len: 期望发送的字节数。
flags: 默认取0。
dest_addr: 目标主机IP。
addrlen: 前一个参数对应结构体的大小,切记该值不取0。
返回值: 成功则返回实际发送的字节数,失败则返回-1。
前面已经介绍过字节序的概念,其中网络字节序采用的是大端模式,而目前的计算机8086平台采用的是小端模式,所以进行网络通信时,还需要一些大小端模式的转换函数,见下表。
函数原型返回值uint32_t htonl(uint32_t hosint32);以网络字节序表示的32位整形数据uint16_t htons(uint16_t hosint16);以网络字节序表示的16位整形数据uint32_t ntohl(uint32_t netint32);以主机字节序表示的32位整形数据uint16_t ntohs(uint16_t netint16);以主机字节序表示的16位整形数据注:h表示主机字节序,n表示网络字节序。l表示长整形,s表示短整形。需要的头文件为#include <arpa/inet.h>
3.基于TCP的网络编程代码
tcp_server.c服务器端程序代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8024
int main()
{
/* 创建socket */
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
perror("socket failed!");
exit(-1);
}
/* 准备地址 */
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
inet_aton("127.0.0.1", &addr.sin_addr);
/* 绑定 */
int res = bind(sockfd, (struct sockaddr *)&addr,sizeof(addr));
if(res == -1)
{
perror("bind failed ");
exit(-1);
}
/* 监听接口 */
if(listen(sockfd, 100) == -1)
{
perror("listen failed ");
exit(-1);
}
/* 等待客户端连接 */
struct sockaddr_in fromaddr; //客户端地址
socklen_t len = sizeof(fromaddr);//注意len初始值一定不为0
int new_sd = accept(sockfd, (struct sockaddr *)&fromaddr, &len);
if(new_sd == -1)
{
perror("accept failed!");
exit(-1);
}
char *from_ip = inet_ntoa(fromaddr.sin_addr);
printf("有一个客户端连接到服务器,他的IP: %s\n",from_ip);
/* 处理客户端数据 */
char buf[100] = {0};
int ret = read(new_sd, buf, sizeof(buf));
if(ret < 0)
{
perror("read failed");
exit(-1);
}
else
{
printf("从客户端读到数据,内容是%s\n", buf);
}
char *str = "欢迎光临";
write(new_sd, str, strlen(str));
/* 关闭连接 */
close(new_sd);
close(sockfd);
return 0;
}
tcp_client.c客户端程序代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define PORT 8024
int main()
{
/* 创建socket */
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
perror("socket failed!");
exit(-1);
}
/* 准备地址 */
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
/* 修改为服务器所在主机IP地址 */
inet_aton("127.0.0.1", &addr.sin_addr);
/* 连接 */
int res = connect(sockfd, (struct sockaddr *)&addr,sizeof(addr));
if(res == -1)
{
perror(" bind failed! ");
exit(-1);
}
/* 收发数据 */
char *str = "你好服务器 ";
write(sockfd,str, strlen(str));
char buf[100] = {0};
read(sockfd,buf, sizeof(buf));
printf("服务器说:%s\n",buf);
/* 关闭 */
close(sockfd);
return 0;
}
通过以下命令编译代码
gcc tcp_server.c -o tcp_server
gcc tcp_client.c -o tcp_client
先执行./tcp_server,再执行./tcp_client运行结果如下:
4.基于UDP的网络编程及代码
UDP是一个无连接协议,在网络交互过程中,不保持连接,只在发送数据时连接。缺点是不能保证数据的完整性和顺序性,优点是资源消耗少。例如,写信寄信、QQ视频、视频会议等都应用了UDP协议。基于UDP协议的网络编程步骤如下:
服务器端编程步骤:
(1)创建套接字socket。
(2) 准备地址。
(3)绑定套接字bind。
(4)收发数据recvfrom sendto。
(5)关闭套接字
客户端编程步骤:
(1)创建套接字。
(2)准备地址。
(3)收发数据recvfrom sendto。
(4)关闭套接字。
基于UDP的网络编程:
udp_server.c服务端程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
/* 创建套接字 */
int sd = socket(AF_INET,SOCK_DGRAM,0);
if(sd == -1)
{
perror(" socket failed ");
exit(-1);
}
/* 准备地址 */
struct sockaddr_in addr;
/* 从&addr开始的sizeof(addr)个字节清空成0 */
memset(&addr, 0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
/* 绑定 */
int res = bind(sd, (struct sockaddr *)&addr, sizeof(addr));
if(res == -1)
{
perror(" bind failed ");
exit(-1);
}
/* 通信 */
while(1)
{
struct sockaddr_in fromaddr;
int len = sizeof(fromaddr);
memset(&fromaddr, 0, sizeof(fromaddr));
char buf[100] = {0};
recvfrom(sd, buf, sizeof(buf), 0, (struct sockaddr *)&fromaddr, &len);
printf("从客户端%s接收到数据: %s\n",inet_ntoa(fromaddr.sin_addr), buf);
char *str = "欢迎光临";
sendto(sd, str, strlen(str), 0, (struct sockaddr *)&fromaddr, sizeof(fromaddr));
}
close(sd);
return 0;
}
udp_client.c客户端程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
/* 创建套接字 */
int sd = socket(AF_INET,SOCK_DGRAM, 0);
if(sd == -1)
{
perror("socket failed ");
exit(-1);
}
/* 准备地址 */
struct sockaddr_in addr;
/* 从&addr开始的sizeof(addr)个字节清空成0 */
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
/* 通信 */
char *str = "你好,我是客户端!\n";
sendto(sd,str,strlen(str),0,(struct sockaddr *)&addr, sizeof(addr));
char buf[100] = {};
int len = sizeof(addr);
recvfrom(sd, buf, sizeof(buf), 0, (struct sockaddr *)&addr, &len);
printf("服务器说:%s\n", buf);
close(sd);
return 0;
}
通过以下命令编译代码:
gcc udp_server.c -o udp_server
gcc udp_client.c -o udp_client
先执行./udp_server,再执行./udp_client运行结果如下:
版权归原作者 ssz__ 所有, 如有侵权,请联系我们删除。