TCP和UCDP套接字编程步骤
编程步骤参考文章链接:/gd-luojialin/p/7694149.html
TCP套接字编程
1)服务端
创建一个socket,用函数socket();
设置socket属性;
绑定ip地址、端口等信息到socket上,用函数bind();
开启监听,用函数listen();
接收客户端上来的连接,用函数accept();
收发数据,用函数send()和recv(),或者read()和write();
关闭网络连接;
关闭监听;
2)客户端
创建一个socket,用函数socket();
设置socket属性,用函数setsockopt();*可选
绑定ip地址、端口等信息到socket上,用函数bind();*可选
设置要连接的对方的ip地址和端口等属性;
连接服务器,用函数connect();
收发数据,用函数send()和recv(),或者read()和write();
关闭网络连接;
UDP套接字编程步骤
1)服务端
创建一个socket,用函数socket();
设置socket属性,用函数setsockopt();*可选
绑定ip地址、端口等信息到socket上,用函数bind();
循环接收数据,用函数recvfrom();
关闭网络连接;
2)客户端
创建一个socket,用函数socket();设置socket属性,用函数setsockopt();*可选绑定ip地址、端口等信息到socket上,用函数bind();*可绑可不绑设置对方的ip地址和端口等属性;发送数据,用函数sendto();关闭网络连接;
套接字编程实例
Linux下使用C语言编程实现TCP/UDP协议下服务端和客户端的通讯的两个实例
实验过程中出现的一些小bug以及解决方法
1)Linux编译时显示权限不够
需要为你要编译的文件提升权力,直接执行chmod 777 文件名即可;
比如你要提升权力的文件名为:TCP_server.c,则直接执行chmod 777 TCP_server.c即可
2)编译后报错:未预期的符号“(”附近有语法错误
问题出在你编译运行.c文件的时候,./TCP_client是不需要在这个文件后缀加.c的
即编译运行Linux下的C语言程序时,只要按照以下步骤来:
先cd到该文件所在的目录(我的c程序文件所在的目录是在桌面)
cd Deskktop
编译c程序文件生成可执行文件(使用gcc编译器)(我的c程序文件名称为TCP_server.c)
gcc -g TCP_server.c -o TCP_server
运行c程序(这里的./表示在当前路径下运行该程序)
./TCP_server
关于Linux下的编译原理,具体可以通过这篇文章粗略了解一下
3)c语言scanf函数的坑
在进行套接字实现大小写转换的实验中,曾试想用键盘输入的方式来生成待传输的字母字符,使用了scanf函数,scanf函数默认会从字符缓冲区读入字符,当需要多次输入数据时,我们习惯输入一个数据按回车作为结束,问题便出在这里,此时作为结束的回车仍然存在缓冲区中,当进入下个scanf循环时,scanf将会把缓冲区中的回车作为字符输入数据,导致出错
提醒自己:scanf在输入数据的时候,若是将数据存入字符串数组,只需连续键入字符串即可,之后每个字符便对应着数组的每一个元素,而不能理解为,要对每一个数组元素都进行一次键入数据,这样就变成你输入一个字符便按回车,导致数据输入错误
int main(){char str[10];scanf("%s", str);printf("%c", str[1]);return 0;}
具体解决方法可参考博文:/view/232.html
4)主函数参数问题
看过很多代码,很多都在主函数中带上了参数,一直不理解是什么意思,在这里记录一下
参考博文链接:/weixin_43053387/article/details/88034871
main( int argc, char* argv[], char *env )
上述主函数写法是UNIX、Linux以及Mac OS操作系统中C/C++的main函数标准写法,并且是血统最纯正的main函数写法。
第一个参数,int型的argc,为整型,用来统计程序运行时发送给main函数的命令行参数的个数,在VS中默认值为1。第二个参数,char型的argv[],为字符串数组,用来存放指向的字符串参数的指针数组,每一个元素指向一个参数。各成员含义如下:
argv[0]指向程序运行的全路径名
argv[1]指向在DOS命令行中执行程序名后的第一个字符串
argv[2]指向执行程序名后的第二个字符串
argv[3]指向执行程序名后的第三个字符串
argv[argc]为NULL第三个参数,char**型的env,为字符串数组。env[]的每一个元素都包含ENVVAR=value形式的字符串,其中ENVVAR为环境变量,value为其对应的值。平时使用到的比较少。
实验中涉及到的函数介绍
1) socket函数
socket()函数用于根据指定的地址簇、数据类型和协议来分配一个套接字接口的描述字及其所用的资源;返回值是一个文件描述符,0,1,2分别表示标准输入,标准输出,标准错误,出错时返回-1
第一个参数AF_INET表示仅支持AF_INET格式,也就是说ARPA Internet地址格式
AF_INET-----IPv4协议
AF_INET6-----IPv6协议
AF_UNIX-----UNIX域协议(文件系统套接字、本地套接字)
第二个参数type参数指定socket类型,如TCP(SOCK_STREAAM)、UDP(SOCK_DGRAM)等等
SOCK_STREAM-----字节流套接字接口,可实现TCP
SOCK_DGRAM-----数据包套接字接口,可实现UDP
SOCK_RAW-----原始套接字,允许对较低层次的协议的直接访问
第三个参数protocol参数,指定协议,若不想指定可置为0,表示默认为TCP/UCDP协议
2) sockaddr_in结构体
此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息。但一般编程中并不直接针对此数据结构操作,而是使用另一个与sockaddr等价的数据结构
具体见程序代码注释
3) bind函数
bind函数把一个本地协议地址赋予一个套接字。第一个参数表示套接字的文件描述符;第二个参数是一个指向特定协议的地址结构的指针,第三个参数是该地址结构的长度。对于TCP,调用bind函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还可以都不指定。返回值0表示成功,-1表示不成功。
4) ntohs函数
ntohs函数的作用是将一个16位数由网络字节顺序转换为主机字节顺序,将一个无符号短整型数从网络字节顺序转换为主机字节顺序,htons()函数返回一个网络字节顺序的值。 这2个函数提供了主机字节顺序与网络字节顺序的转换
网络字节顺序NBO(Network Byte Order): 按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题。
主机字节顺序(HBO,Host Byte Order): 不同的机器HBO不相同,与CPU设计有关,数据的顺序是由cpu决定的,而与操作系统无关。
5) listen函数
第一个参数是服务端套接字,第二个参数是等待连接队列的最大长度;比如说,第二个参数为10时,当有15个连接请求时,前面10个连接请求就被放置在请求队列中,后面5个请求被拒绝,返回值0表示成功,-1表示不成功。
6)accept函数
第一个参数表示服务端的socket描述符,第二个参数表示指向struct sockaddr的指针,用于返回客户端的协议地址,第三个参数返回协议地址的长度;成功则返回新的套接字描述符,失败则返回-1
7) read函数
操作系统内核从套接字描述字 socketfd读取最多多少个字节(size),并将结果存储到 buffer 中。返回值告诉我们实际读取的字节数目。同时,在阻塞模式下,没读满size个字节之前,会一直循环读取下去,阻塞读取直至读到size个字节的数据,同时,read函数读取的数据暂时存放在缓冲区之中,直至读取满size个字节的数据才返回是数据到服务端/客户端。如果此时套接字缓冲区内没有数据可读,则调用线程在数据到来前一直睡眠。成功返回读取的字节数,出错返回-1并设置errno,如果在调read之前已到达文件末尾,则这次read返回0。
大多数情况下,函数调用都会调用“失败”(非阻塞模式下),并返回WSAEWOULDBLOCK错误代码。说明请求的操作在调用期间内没有时间完成。通常,应用程序需要重复调用该函数,直到获得成功返回代码。
ps: 当使用socket()函数和WSASocket()函数创建套接字时,默认都是阻塞的。
关于阻塞模式和非阻塞模式可参考博文链接:/qq_42362440/article/details/92790985
8) write函数
内核从该应用进程的缓冲区中复制所有数据到所写套接字的发送缓冲区;阻塞模式下如果套接字的发送缓冲区没有空间,则应用进程阻塞,直到有空间为止(TCP);由于UDP套接字不存在真正的发送缓冲区,内核只是复制应用进程数据并把它沿协议栈向下传送,因此对于一个UDP套接字不会因与TCP一样的原因将其阻塞。非阻塞模式下,如果非阻塞的套接字的发送缓存没有空间,则函数立即返回EWOULDBLOCK(或EAGIN)错误;如果其发送缓冲区中有一些空间,返回值是内核能够复制到该缓冲区中的字节数。
read函数和write函数在阻塞模式下和非阻塞模式下的区别,参考博文链接:/seu_lyr/article/details/11492957
9) recvfrom函数
recvfrom()从(已连接)套接口上接收数据,并捕获数据发送源的地址。最多可接收缓冲区大小个数据。如果数据报大于缓冲区,那么缓冲区中只有数据报的前面部分,其他的数据都丢失了,并且recvfrom()函数返回WSAEMSGSIZE错误。第一个参数标识一个已连接的套接字的文件描述符;第二个参数标识接收数据缓冲区;第三个参数标识接收数据缓冲区的长度;第四个参数标识调用操作方式,一般设置为0;第五个参数标识一个指针,指向装有源地址的缓冲区;第六个参数标识一个指针,指向装有源地址缓冲区的长度。
10) sendto函数
指向一指定目的地发送数据,sendto()适用于发送(未建立连接)的UDP数据包。第一个参数标识了发送方的套接字文件描述符;第二个参数标识了待发送数据的缓冲区;第三个参数标识了缓冲区的长度;第四个参数标识了调用方式标志,一般为0;第五个参数标识了一个指针,指向目的套接字地址;第六个参数标识了一个指针,指向目的套接字地址的长度。返回值为整型,如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR。
recvfrom函数和sendto函数都有两种模式,阻塞模式和非阻塞模式,阻塞模式是一直等待直到有数据到达,非阻塞模式是立即返回
recvfrom函数/recv函数和sendto函数/send函数区别和联系比较,参考博文链接:/fengxianghui01/article/details/104398214
11) connect函数
第一个参数是客户端调用socket函数返回的文件描述符,第二个参数是要连接的对端套接字地址结构,第三个参数是对端套接字地址结构的大小。成功则返回0,失败则返回-1,并设置errno的值。
tcp和udp在编程中的差别
参考博文链接:/gd-luojialin/p/7694149.html
1)udp程序结构较为简单,tcp相对来说要更复杂一点
2)udp server不需要调用listen(监听)和accept函数(在建立起连接之后接收客户端数据),只要指定客户端ip地址和端口号即可向客户端发送数据
3)udp收发数据使用sendto/recvfrom函数,而tcp使用write/read或recv/send函数
4)tcp的地址信息在connect/accept函数时进行确定,建立起一个专用通道,而udp在sendto/recvfrom函数中每次均需指定地址信息,才能使数据准确的在服务端和客户端进行收发
5)tcp保证数据正确性,udp可能会丢包
6)tcp保证数据顺序,udp不保证
7)tcp的socket函数第二个参数为SOCK_STREAM(表示建立一个socket用于流式网络通讯,同时是面向连接的,每次收发数据之前都必须通过connect建立连接,连接是双向的),udp的socket函数第二个参数为SOCK_DGRAM(表示建立一种无连接的,不可靠的,通讯双方发送数据后不知道对方是否已经接收到数据,是否正常收到数据,任何一方建立一个socket以后就可以用sendto进行数据的发送,用recvfrom进行数据的接收,不关心对方是否存在,是否发送了数据,但是udp通讯速度快)