LINUX下的Socket网络编程TCP/IP
Posted 遥远的歌s
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LINUX下的Socket网络编程TCP/IP相关的知识,希望对你有一定的参考价值。
网络
学习过linux后了解到,在linux系统下的网络编程很有意思,在此,先要说一下TCP/IP,它是一个面向连接且可靠的一组协议,并且是全双工通信。它的存在使得网络世界缤纷多彩。linux内核将会提供一系列函数帮助我们完成网络编程。
想要对网络编程有认识,首先要知道其建立连接,数据传送,断开连接等过程。
我们知道目前从低向上公认有5层分别为:物理层,数据链路层,运输层,网络层,应用层。 这里主要讨论运输层即TCP层。
三次握手(三路握手)
我们知道,建立一个TCP连接需要经过三次握手的情形。
- 服务器必须准备好接受外来的连接。这通常通过调用socket,bind和listen这三个函数来完成(下文会讲到这三个函数),这里称之为被动打开(passive open)。
- 客户通过调用connect发起主动打开(active open)。这导致客户TCP发送一个SYN分节,它告诉服务器客户将在(待建立的)连接中发送的数据的初始序列号。通常SYN分节不携带数据,其所在IP数据包只含有一个IP首部,一个TCP首部以及可能有的TCP选项。
- 服务器必须确定(ACK)客户的SYN,同时自己也得发送一个SYN分节,它含有服务器将在同一连接中发送的数据的初始序列号。服务器在单个分节中发送SYN和对客户SYN的ACK(确认)。
- 客户必须确定服务器的SYN。
这种交换至少需要3个分组,因此称之为TCP的三路握手。如下图
四次挥手
TCP的连接终止则需要4个分节
- 某个应用进程首先调用**close,**我们称该端执行主动关闭(active close)。该端的TCP于是发送一个FIN分节,表示数据发送完毕。
- 接收到这个FIN的对端执行被动关闭(passive close)。这个FIN由TCP确认。它的接收也作为一个文件结束符( end-of-file)传递给接收端应用进程(放在已排队等候该应用进程接收的任何其他数据之后),因为FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收。
- 一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字。这导致它的TCP也发送一个FIN。
- 接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN。
既然每个方向都需要一个FIN和一个ACK,因此通常需要4个分节。我们使用限定词“通常”是因为: 某些情形下步骤1的FIN随数据一起发送;另外,步骤2和步骤3发送的分节都出自执行被动关闭那一端,有可能被合并成一个分节。
过程如下图
下图为一个完整的TCP连接所发生的实际分组交换情况,包含建立连接,数据交换,连接终止三个阶段。
基本TCP套接字编程
这里主要采用C/S体系,即客户/服务器程序。想要执行网络的I/O,则一个进程必须做的第一件事就是调用socket函数,并且指定期望的协议类型(IPv4的TCP,IPv6的UDP,Unix域字节流协议等)。
一、服务端
1.socket函数
用来创建一个监听套接字,用来知道谁要连我
所在头文件:#include<sys/socket.h>
函数体:int socket(int family, int type, int protocol);
返回:成功则返回非负描述符,出错则为-1
其中family参数指明协议族,它通常是下述的某个常数值:
type参数指明套接字的类型,它通常是下属的某个值:
protocol参数则应当设为如下所示的某个协议类型的长治,或设置为0,从而以选择所给定的family和type组合系统所给的默认值。
socket函数在成功执行后会返回一个小的飞赴整数值,它与文件描述符类似,我们把它称之为套接字描述符,为了得到这个套接字描述符,我们只是指定来协议族和套接字类型,并没有指定本地协议地址或远程协议地址。
2.bind函数
bind函数把一个本地协议地址赋予一个套接字
所在头文件:#include<sys/socket.h>
函数体:int bind(int sockfd, const struct sockaddr * myaddr, socklen_t addrlen);
返回:成功则返回0,出错则为-1
上面socket函数返回的套接字描述符,其本质就是一个文件描述符,只不过不同的是他是一个指向网络的描述符,通过一系列指向最终指向了内核的struct sock结构体,如下图:
既然套接字已经创建好了,接下来就是想要让别人连接我,那么我必须公开我的IP地址以及端口号,那么bind函数就起到了这样的作用,它用来绑定一个IP地址和端口,便于别人来连接我们。
第一个参数:sockfd就是socket函数成功返回的监听套接字的值。
第二个参数:指向struct sockaddr结构体的一个指针,struct sockaddr是一个通用结构体,因为内核并不知道我要绑定的是的IPv4的还是IPv6的甚至是绑定别的东西,因此这里设计一个通用的结构体,便于编程者去决定要绑定的东西。例如IPv4的结构体如下:
struct sockaddr_in
sa_family_t sin_family;/*地址族:AF_INET*/
u_int16_t sin_port;/*按网络字节序的端口*/
struct in_addr sin_addr;/*internet地址,即IP地址*/
/*internet地址*/
struct in_addr
u_int32_t s_addr/*按网络字节序的地址*/
第三个参数:addrlen 则是第二个参数的大小。
这里值得一提的是,既然通过网络传输数据,那如果两方的机器的字节序不一样那么传输的数据是不能用的,因此规定网络上传输数据都要用网络字节序,这里系统已经提供好了一套字节序转换的函数:
#include <arpa/inet.h>
//本机字节序转换尾网络字节序
uint32_t htonl(uint32_t hostlong);//32位
uint16_t htons(uint16_t hostshort);//16位
//网络字节序转换尾本机字节序
uint32_t ntohl(uint32_t netlong);//32位
uint16_t ntohs(uint16_t netshort);//16位
h:host n:net l:int s:short
绑定好后,即相当于我已经公布了我的IP地址和端口号,可以让别人来连接我了,但是这里还是不能连接的,要通过listen函数,转换为被动套接字。
listen函数
listen函数是将监听套接字转换为被动套接字。被动套接字的目的是用来让别人连接我们。
所在头文件:#include<sys/socket.h>
函数体:int listen(int sockfd, int backlog)
第一个参数:sockfd为监听套接字
重点是第二个参数:backlog
listen函数会创建两个队列,分别是未完成三次握手的队列和已完成三次握手的队列,在一些书上说明backlog为这两个队列的大小之和,不过,现在已经改为已完成三次握手队列的大小。
当已完成三次握手后,就会调用accept函数。
accept函数
accept函数由TCP调用,用域从已完成三次握手连接的队列对头返回下一个已完成的连接。
这里一个较好的理解就是,比如我拨打卖房电话为78888,那么当我拨打通后,会被转接,这样的话另外一个人拨打这个电话的时候依旧是能拨通的,那么78888就相当于一个监听套接字,用来知道都有哪些人给我打电话了,而拨通后的转接,则相当于真正的连接套接字,用来正真和人通话的主机。所以accept函数内部会从新调用一个socket函数创建一个新的套接字,用来真正的连接通话
所在头文件:#include<sys/socket.h>
函数体:int accept(int sockfd , struct sockaddr * cliaddr, socketlen_t addrlen)
返回:成功则返回非负描述符,出错则为-1
accept函数是一个阻塞函数,当连接建立后,内核会给出一个信号,唤醒这个阻塞函数,去创建一个新的socket,那就需要知道我的ID地址和端口,所以第二个参数仍然是指向struct sockaddr这个结构体的一个指针,第三个参数则是这个结构体的大小。当accept函数调用成功后,则服务端只需等待客户端就连接上来能开始数据传送和处理数据啦!
二、客户端
当服务端已经完成后,则需要创建客户端。
客户端首先同样需要调用socket函数,既然是要连接服务端,则需要一个套接字来进行连接。这里不需要bind函数,就好比我打110,我拿任何一个电话都可以打110,并不规定我打110的号码只能是某一个。
调用完socket函数后,客户端只需要在调用connect函数进行与服务端的连接。
connect函数
这个函数是用来和服务端进行连接的。如果是TCP套接字的话,则这个函数将激发TCP的三路握手,并且仅在连接成功或出错时才返回。
所在头文件:#include<sys/socket.h>
函数体:int connect(int sockfd, const struct sockaddr * servaddr, socklen_t addrlen);
返回:成功则为0,出错则为-1
connect函数的第一个参数是我上面调用socket返回的套接字,第二个参数则是服务器的IP地址和端口号的,即指向struct sockaddr结构体的一个指针,最后一个参数则是这个结构体的大小。
整个过程如下图:
基于CentOS7系统下的编译环境
服务端代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//创建监听套接字
int creat_socket()
int lfd = 0;
lfd = socket(AF_INET,SOCK_STREAM,0);
if(lfd == -1)//如果出错
perror("socket");
exit(1);
printf("监听套接字创建成功\\n");
return lfd;
//服务端将客户端发来的数据中所有的小写字母转换为大写字母
void change(char *p,int size)
for(int i = 0;i<size;i++)
if(p[i] >= 'a' && p[i] <= 'z')
p[i] = p[i] - 32;
int main()
int lfd = creat_socket();、
//确定ip地址和端口
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(6666);
inet_aton("192.168.66.66",&addr.sin_addr);
//绑定
int r = bind(lfd,(struct sockaddr*)&addr,sizeof(addr));
printf("绑定成功\\n");
if(r == -1)
perror("bind");
exit(1);
//转换为被动套接字
if((r = listen(lfd,SOMAXCONN)) == -1)
perror("listen");
exit(1);
printf("被动套接字创建成功,等待连接\\n");
int newfd = accept(lfd,NULL,NULL);//进行连接
printf("有客户端连接上来\\n");
while(1)
char buf[1024] = ;
r = read(newfd,buf,1024);
if(r == 0)
break;
printf("%s\\n",buf);
change(buf,strlen(buf));
write(newfd,buf,r);
close(lfd);
close(newfd);
printf("服务器已断开\\n");
return 0;
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
int lfd = socket(AF_INET,SOCK_STREAM,0);//创建套接字
if(lfd == -1)
perror("socket");
exit(1);
//填入我要连接的IP地址和端口号
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(6666);
inet_aton("192.168.66.66",&addr.sin_addr);
//与服务端进行连接
int r = connect(lfd,(struct sockaddr*)&addr,sizeof(addr));
if(r == -1)
perror("connect");
exit(1);
char buf[1024] = ;//读取缓冲区
printf("与服务器连接成功\\n");
while(fgets(buf,1024,stdin) != NULL)//从键盘上读取至buf不为空
write(lfd,buf,strlen(buf));//写入lfd中
memset(buf,0x00,sizeof(buf));
r = read(lfd,buf,1024);//再从lfd读取
if(r == 0)//表示连接断开
close(lfd);
break;
printf("%s\\n",buf);
memset(buf,0x00,sizeof(buf));
return 0;
运行结果
开启服务端
开启客户端
进行数据读取和处理
参考:UNIX网络编程卷1:套接字联网API(第3版)
以上是关于LINUX下的Socket网络编程TCP/IP的主要内容,如果未能解决你的问题,请参考以下文章