今天继续对socket编程进行研究,这里会真正开如用socket写一个小例子,进入正题:

TCP客户/服务器模型:

linux网络编程之socket编程(二)_客户端


关于这个模型的流程这里就不多说了,比较容易理解,下面则利用这种模型来编写一个实际的例子。

回射客户/服务器:linux网络编程之socket编程(二)_客户端_02

这个例子的效果就是:客户端从命令行获取一行命令,然后发送给服务器端,当服务端接收到这行命令之后,不做任何操作,将其又回送给客户端,然后客户端进行回显,下面则开始一步步来实现这样的效果,来初步感受下Socket编程: 首先编写服务端:echosrv.c 第一步:创建套接字:

linux网络编程之socket编程(二)_客户端_03linux网络编程之socket编程(二)_#include_04

关于第一个参数domain,man帮助中也有说明:

linux网络编程之socket编程(二)_客户端_05

但是,AF_INET等价于PF_INET,这里推荐用后者,因为刚好代表protocol family含义,下面代码如下: linux网络编程之socket编程(二)_#include_06  

第二步:绑定一个地址到套接字上:linux网络编程之socket编程(二)_#include_07linux网络编程之socket编程(二)_套接字_08

 首先准备一下第二个参数,也就是要绑定的地址:

linux网络编程之socket编程(二)_#include_09

其中绑定地址还有其它两种方式:

linux网络编程之socket编程(二)_套接字_10

另外,其实"servaddr.sin_addr.s_addr = htonl(INADDR_ANY);"这种写法是可以省略掉的,因为它是全0,但这里为了显示说明所以保留。

下面开始进行绑定:

linux网络编程之socket编程(二)_#include_11

第三步:则开始进行监听:

linux网络编程之socket编程(二)_客户端_12

linux网络编程之socket编程(二)_客户端_13

 linux网络编程之socket编程(二)_客户端_14

linux网络编程之socket编程(二)_客户端_15

具体代码如下:

linux网络编程之socket编程(二)_#include_16

其中SOMAXCONN可以从man帮助中查看到:

linux网络编程之socket编程(二)_套接字_17

它代表了socket的并发最大连接个数。

另外还得注意,套接字有被动套接字和主动套接字之分,当调用listen之后,该socket就变动被动套接字了,需要由主动套接字来发起连接,主动套接字是用connect函数来发起连接的。


第四步:从已完成连接队列中返回第一个连接:

linux网络编程之socket编程(二)_套接字_18


linux网络编程之socket编程(二)_#include_19

linux网络编程之socket编程(二)_套接字_20

接下来,则进行数据的接收,并将数据回显给客户端:

accept函数会返回一个新的套接字,注意:此时的套接字不再是被动套接字,而变为了主动:

linux网络编程之socket编程(二)_客户端_21

可以通过accept的man手册来得知:

linux网络编程之socket编程(二)_套接字_22

下面,则开始从该套接字中读取客户端发过来的数据:

linux网络编程之socket编程(二)_客户端_23

至此,服务端的代码都已经编写完了,下面则先编译一下:

linux网络编程之socket编程(二)_套接字_24

查看man帮助:

linux网络编程之socket编程(二)_#include_25

于是在代码中加入头:

linux网络编程之socket编程(二)_套接字_26

再次编译:

linux网络编程之socket编程(二)_客户端_27

还是出错,那IPPPOTO_TCP是在哪定义的呢?

可以通过以下命令进行查找:

linux网络编程之socket编程(二)_套接字_28

于是乎,加上该头文件后再编译:

linux网络编程之socket编程(二)_套接字_29

linux网络编程之socket编程(二)_#include_30

用同样的办法来进行查找:

linux网络编程之socket编程(二)_套接字_31

于是加入它:

linux网络编程之socket编程(二)_套接字_32

再次编译:

linux网络编程之socket编程(二)_套接字_33

还是报错,对于这里面对应的头文件这里就不具体一个个查找了,不然有点充数的嫌疑,将所有头文件加上再次编译:

linux网络编程之socket编程(二)_套接字_34


接下来,开始编写客户端的代码:echocli.c

首先创建一个socket:

linux网络编程之socket编程(二)_套接字_35

linux网络编程之socket编程(二)_#include_36

第二步开始与服务器进行连接:

linux网络编程之socket编程(二)_#include_37

linux网络编程之socket编程(二)_套接字_38


linux网络编程之socket编程(二)_#include_39

【说明】:用connect发起连接的套接字是主动套接字。

连接成功之后,就可以向服务器发送数据了:

linux网络编程之socket编程(二)_客户端_40

另外,服务端在使用资源之后,最后也得关闭掉,所以修改服务端程序如下:

linux网络编程之socket编程(二)_客户端_41

这时,客户端程序也已经编写完成,下面编译运行看一下效果:

linux网络编程之socket编程(二)_套接字_42

也就是第一次客户端输入很长的字符串,而第二次输入很短的字符串时,这时就会输出有问题,照理应该是客户端输入什么,服务端就会回显给客户端,也就是打印两条一模一样的语句,产生这样的问题原因是什么呢?

下面来用图来分析一下:


linux网络编程之socket编程(二)_#include_43

linux网络编程之socket编程(二)_#include_44

所以,解决该问题的思路就是每次循环时将值初始化一下既可,修改代码如下:

linux网络编程之socket编程(二)_套接字_45

 再次编译运行:

linux网络编程之socket编程(二)_套接字_46

关于这个原因,之后会来解决,先占且不关心,等一会就会正常了,正常运行的效果如下:

linux网络编程之socket编程(二)_客户端_47

这样,就实现了客户端与服务器端的socket通讯了,最终的代码如下:

echosrv.c【服务端】:


#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)

int main(void)
{
int listenfd;
if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
/* if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)*/
ERR_EXIT("socket");

struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
/*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/
/*inet_aton("127.0.0.1", &servaddr.sin_addr);*/

if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind");
if (listen(listenfd, SOMAXCONN) < 0)
ERR_EXIT("listen");

struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
int conn;
if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
ERR_EXIT("accept");

char recvbuf[1024];//用来存储客户端发来的数据
while (1)
{
memset(recvbuf, 0, sizeof(recvbuf));
int ret = read(conn, recvbuf, sizeof(recvbuf));//从套接字中读取数据
fputs(recvbuf, stdout);//打印到屏幕上
write(conn, recvbuf, ret);//并且将其又回显给客户端,其第三个参数的长度正好是我们接收到的长度
}
close(conn);
close(listenfd);

return 0;
}


echocli.c【客户端】:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)

int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
ERR_EXIT("socket");

struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("connect");

char sendbuf[1024] = {0};
char recvbuf[1024] ={0};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
write(sock, sendbuf, strlen(sendbuf));
read(sock, recvbuf, sizeof(recvbuf));

fputs(recvbuf, stdout);
memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
}

close(sock);

return 0;
}


好了,今天的内容学到这,下回见~