在Windows下利用C语言实现socket通信,刚开始学习,记录自己的学习过程,如果出现错误还望各位大佬指点一二。
一、原理

1.socket()函数
int socket(int domain, int type, int protocol);
domain:协议域,决定了socket的地址类型,在通信中必须采用对应的地址。
type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。
protocol:指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等。
|
名称 |
目的 |
|
AF_UNIX, AF_LOCAL |
本地通信 |
|
AF_INET |
IPv4网络通信 |
|
AF_INET6 |
IPv6网络通信 |
|
AF_PACKET |
链路层通信 |
在Linux系统中AF_*和PF_*是等价的。
更多的socket函数的domain、type、protocol解析详见https://blog.csdn.net/liuxingen/article/details/
2.bind()函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。
addrlen:对应的地址长度。
3.listen(),connect()函数
int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。
listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。
connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。
4.accept()函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。
5.read()、write()函数
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()
以recv()为例
int PASCAL FAR recv( SOCKET s, char FAR* buf, int len, int flags);
s:一个标识已连接套接口的描述字。
buf:用于接收数据的缓冲区。
len:缓冲区长度。
flags:指定调用方式
6.close()函数
int close(int fd);
7. sockaddr_in和sockaddr
处理网络通信的地址
sockaddr在头文件#include
socket.h
>
中定义,
sockaddr
的缺陷是:
sa_data
把目标地址和端口信息混在一起
sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义,该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中
二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。
sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息,是一种通用的套接字地址。
sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数。
注:这两个函数的头文件
Linux下
#include <netinet/in.h>
#include
socket.h
>
Windows下
#include
#pragma comment(lib,”Ws2_32.lib “)
https://blog.csdn.net/will130/article/details/
关于这些函数更详细的总结参考https://blog.csdn.net/hellokitty136/article/details/
socket各种错误码
1.INVALID_SOCKET : 表示该 socket fd 无效。如 accept(2) 或 socket(2) 等在创建socketfd时
2.SOCKET_ERROR : 如调用bind(2)、listen(2)、connect(2)、send(2)、setsockopt(2)、fcntl(2)等函数时出错则会返回该宏
3.socket在非阻塞的情况下接收和发送数据时的错误码以及如何处理:
(1)判断socket是否“阻塞”(即检测错误类型 errno 是否为 EINPROGRESS 或 EWOULDBLOCK,该情况往往是由于TCP窗口导致的)
(2)若错误码 errno 为 EINTR(被信号中断了,则继续重试),则忽略该错误,继续接收或发送数据。
(3)若recv或send调用后返回 0 ,则表示对端关闭了连接。
更多的详见https://blog.csdn.net/u0/article/details/
二、实现
2.1 server
#include
#include
#include
#pragma comment(lib, "ws2_32.lib") #define PORT 1500; //端口号 #define BACKLOG 5; //最大监听数 int main(int argc, char* argv[]) { //初始化WSA windows下异步套接字的启动命令 WORD sockVersion = MAKEWORD(2, 2); WSADATA wsaData; if (WSAStartup(sockVersion, &wsaData) != 0) { return 0; } //创建套接字 SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (slisten == INVALID_SOCKET) { printf("socket error !"); return 0; } //绑定IP和端口 sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(8888); sin.sin_addr.S_un.S_addr = INADDR_ANY; if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) { printf("bind error !"); } //开始监听 if (listen(slisten, 5) == SOCKET_ERROR) { printf("Listen error!\n"); return 0; } //循环接收数据 SOCKET sClient; sockaddr_in remoteAddr; int nAddrlen = sizeof(remoteAddr); char revData[255]; while (true) { printf("等待连接……\n"); sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen); if (sClient == INVALID_SOCKET) { printf("accept error !"); continue; } printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr)); //接收数据 int ret = recv(sClient, revData, 255, 0); if (ret > 0) { revData[ret] = 0x00; printf(revData); } //发送数据 char * sendData = "TCP客户端!\n"; send(sClient, sendData, strlen(sendData), 0); closesocket(sClient); } closesocket(slisten); WSACleanup(); system("pause"); return 0; }
2.2 client
#include
#include
#pragma comment(lib,"ws2_32.lib") int main(int argc, char* argv[]) { //初始化WSA WORD sockVersion = MAKEWORD(2, 2); WSADATA data; if (WSAStartup(sockVersion, &data) != 0) { return 0; } //创建套接字 SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sclient == INVALID_SOCKET) { printf("invalid socket !"); return 0; } //绑定IP和端口 sockaddr_in serAddr; serAddr.sin_family = AF_INET; serAddr.sin_port = htons(8888); serAddr.sin_addr.S_un.S_addr = inet_addr("192.168.0.102");//127.0.0.1 本地地址或者127.0.0.1都可以 if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR) { printf("connect error !"); closesocket(sclient); system("pause"); return 0; } //发送数据 char * sendData = "TCP服务端,我是客户端!\n"; send(sclient, sendData, strlen(sendData), 0); printf(sendData); char recData[255]; int ret = recv(sclient, recData, 255, 0); if (ret > 0) { recData[ret] = 0x00; printf(recData); } //关闭 closesocket(sclient); WSACleanup(); system("pause"); return 0; }
代码实现参考https://www.cnblogs.com/churi/archive/2013/02/27/2935427.html
三、调试运行
使用vs2015
首先,执行server程序
然后,在右侧解决方案一栏中单击选择client解决方案,右键->调试->启动新实例,即可运行client程序。
3.1错误及解决
a.C4996 ‘inet_addr’: Use inet_pton() or InetPton() instead or define _WINSOCK_DEPRECATED_NO_WARNINGS to disable deprecated API warnings SocketClient g:\c\oppo\socketclient\client.cpp 27
项目->属性->c/c++->SDL检查 改成 否(/sdl-)
b.一直显示“connect error”
server和client的端口设置不一致,serAddr.sin_port = htons(8888);更改为一个端口号即可;
inet_addr()设置错误,设置为本地IP或者127.0.0.1都可以。
有可能是防火墙的问题,关闭防火墙进行调试。(我没有关闭也能执行,看一些文章中说的需要关闭)
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/211141.html原文链接:https://javaforall.net
